spec_tracker 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +48 -0
  5. data/CODE_OF_CONDUCT.md +76 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +79 -0
  8. data/LICENSE +24 -0
  9. data/README.md +84 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/exe/spec_tracker +5 -0
  14. data/lib/spec_tracker.rb +50 -0
  15. data/lib/spec_tracker/cli.rb +24 -0
  16. data/lib/spec_tracker/config/configuration.rb +23 -0
  17. data/lib/spec_tracker/config/en.yml +8 -0
  18. data/lib/spec_tracker/config/fr.yml +8 -0
  19. data/lib/spec_tracker/report_task.rb +26 -0
  20. data/lib/spec_tracker/reporter/base_reporter.rb +24 -0
  21. data/lib/spec_tracker/reporter/base_ui_mapper.rb +25 -0
  22. data/lib/spec_tracker/reporter/report.rb +17 -0
  23. data/lib/spec_tracker/reporter/report_datum.rb +13 -0
  24. data/lib/spec_tracker/reporter/report_mapper.rb +19 -0
  25. data/lib/spec_tracker/reporter/status_ui.rb +8 -0
  26. data/lib/spec_tracker/reporter/terminal/reporter.rb +28 -0
  27. data/lib/spec_tracker/reporter/terminal/ui_mapper.rb +13 -0
  28. data/lib/spec_tracker/spec_parser/base_parser.rb +36 -0
  29. data/lib/spec_tracker/spec_parser/csv_parser.rb +24 -0
  30. data/lib/spec_tracker/spec_parser/gherkin_parser.rb +27 -0
  31. data/lib/spec_tracker/spec_parser/scenario.rb +12 -0
  32. data/lib/spec_tracker/spec_parser/specification.rb +12 -0
  33. data/lib/spec_tracker/test_report_parser/base_mapper.rb +52 -0
  34. data/lib/spec_tracker/test_report_parser/base_parser.rb +43 -0
  35. data/lib/spec_tracker/test_report_parser/j_unit/mapper.rb +25 -0
  36. data/lib/spec_tracker/test_report_parser/j_unit/parser.rb +31 -0
  37. data/lib/spec_tracker/test_report_parser/test_result.rb +12 -0
  38. data/lib/spec_tracker/test_status.rb +46 -0
  39. data/lib/spec_tracker/version.rb +3 -0
  40. data/spec_tracker.gemspec +39 -0
  41. data/specifications/Spec Fonctionnelles SpecTracker - Configuration.csv +11 -0
  42. data/specifications/Spec Fonctionnelles SpecTracker - Parse specifications.csv +20 -0
  43. data/specifications/configuration.feature +11 -0
  44. data/specifications/parse_csv_specifications.feature +11 -0
  45. data/specifications/parse_gherkin_specifications.feature +11 -0
  46. metadata +249 -0
@@ -0,0 +1,24 @@
1
+ module SpecTracker
2
+ class CLI < Thor
3
+ desc 'report', 'generate a report for a given configuration (default or custom)'
4
+ method_option :spec_path, type: :string, desc: "Path to the specifications. Expects a relative path to a file or a directory. Default: specifications"
5
+ method_option :test_report_path, type: :string, desc: "Path to the test reports. Expects a relative path to a file or a directory. Default: test/reports"
6
+ method_option :scenario_id_regex, type: :string, desc: "Regexp to find scenario ID in test reports. Default : \\[([a-zA-Z\\-]+)\\]"
7
+ method_option :spec_type, enum: %w(csv gherkin), desc: "Type of specification files. Default : csv"
8
+ method_option :scenario_id_header, type: :string, desc: "Name of the scenario ID column when spec files are csv. Default: #Scenario"
9
+ method_option :scenario_name_header, type: :string, desc: "Name of the scenario name column when spec files are csv. Default: Name/Rule"
10
+ method_option :locale, enum: %w(fr en), desc: "Locale for text output. Default: fr"
11
+ def report
12
+ SpecTracker.configure do |configuration|
13
+ configuration.spec_path = options[:spec_path] if options[:spec_path]
14
+ configuration.test_report_path = options[:test_report_path] if options[:test_report_path]
15
+ configuration.scenario_id_regex = Regexp.new(options[:scenario_id_regex]) if options[:scenario_id_regex]
16
+ configuration.spec_type = options[:spec_type] if options[:spec_type]
17
+ configuration.scenario_id_header = options[:scenario_id_header] if options[:scenario_id_header]
18
+ configuration.scenario_name_header = options[:scenario_name_header] if options[:scenario_name_header]
19
+ configuration.locale = options[:locale] if options[:locale]
20
+ end
21
+ SpecTracker::ReportTask.new.execute
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module SpecTracker
2
+ class Configuration
3
+ attr_accessor :scenario_id_header, :scenario_name_header, :locale,
4
+ :test_report_path, :spec_path, :scenario_id_regex, :spec_type
5
+
6
+ attr_reader :wording, :local_path
7
+
8
+ def initialize
9
+ @scenario_id_header = 'Scenario ID'
10
+ @scenario_name_header = 'Name/Rule'
11
+ @spec_path = 'specifications'
12
+ @test_report_path = 'test/reports'
13
+ @scenario_id_regex = /\[([a-zA-Z\-]+)\]/
14
+ @spec_type = 'csv'
15
+ @available_types = %w(csv gherkin)
16
+ @locale = 'fr'
17
+ @available_locales = %w(fr en)
18
+ wording_path = Pathname.new(__FILE__).join("../#{locale}.yml")
19
+ @wording = YAML.load_file(wording_path)[:"#{locale}"]
20
+ @local_path = Pathname.new(Dir.pwd)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ :en:
2
+ :missing_specs: Missing requirements files
3
+ :bad_format: The requirement file is not a csv
4
+ :too_many_arguments: Too many arguments
5
+ :scenario_id: Scenario ID
6
+ :scenario_name: Scenario Name
7
+ :test_result: Test Result
8
+ :progression: Progression
@@ -0,0 +1,8 @@
1
+ :fr:
2
+ :missing_specs: Le fichier de spécification n'a pas été fourni
3
+ :bad_format: Le fichier de specs n'est pas un csv
4
+ :too_many_arguments: Trop d'arguments ont été fournis
5
+ :scenario_id: Identifiant du scénario
6
+ :scenario_name: Nom du scénario
7
+ :test_result: Statut du test
8
+ :progression: Progression
@@ -0,0 +1,26 @@
1
+ module SpecTracker
2
+ class ReportTask
3
+ attr_reader :spec_parser, :test_report_parser, :report_mapper, :reporter
4
+
5
+ def initialize
6
+ spec_type = SpecTracker.configuration.spec_type
7
+ @spec_parser = (spec_type == %q{gherkin}) ? SpecParser::GherkinParser.new : SpecParser::CSVParser.new
8
+ @test_report_parser = TestReportParser::JUnit::Parser.new
9
+ @report_mapper = Reporter::ReportMapper.new
10
+ @reporter = Reporter::Terminal::Reporter.new
11
+ end
12
+
13
+ def execute
14
+ specifications = spec_parser.parse(SpecTracker.configuration.spec_path)
15
+ test_results = test_report_parser.parse(SpecTracker.configuration.test_report_path)
16
+ specifications.map do |specification|
17
+ report = report_mapper.map(
18
+ topic: specification.topic,
19
+ scenarios: specification.scenarios,
20
+ test_results: test_results
21
+ )
22
+ reporter.print(report)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ class BaseReporter
4
+ def initialize
5
+ # Should initialize UIMapper
6
+ raise Error.new('override me!')
7
+ end
8
+
9
+ def print(_)
10
+ raise Error.new('override me!')
11
+ end
12
+
13
+ protected
14
+
15
+ def view_models(report_data)
16
+
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :ui_mapper
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ class BaseUIMapper
4
+ include StatusUI
5
+
6
+ def map(_)
7
+ raise Error.new('override me!')
8
+ end
9
+
10
+ protected
11
+
12
+ def status_to_ui(status)
13
+ if status.success?
14
+ SUCCESS_EMOJI
15
+ elsif status.failure?
16
+ FAILURE_EMOJI
17
+ elsif status.skipped?
18
+ SKIPPED_EMOJI
19
+ elsif status.missing?
20
+ MISSING_EMOJI
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ class Report
4
+ attr_reader :data, :topic
5
+
6
+ def initialize(data:, topic:)
7
+ @data = data
8
+ @topic = topic
9
+ end
10
+
11
+ def progression
12
+ success_count = data.select {|report_datum| report_datum.status.success?}.size
13
+ (success_count.to_f / data.size.to_f * 100).round(1)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ class ReportDatum
4
+ attr_reader :scenario_id, :scenario_name, :status
5
+
6
+ def initialize(scenario_id:, scenario_name:, status:)
7
+ @scenario_id = scenario_id
8
+ @scenario_name = scenario_name
9
+ @status = status
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ class ReportMapper
4
+ def map(scenarios:, test_results:, topic:)
5
+ report_data = scenarios.map do |scenario|
6
+ test_result = test_results.detect {|test_result| test_result.scenario_id == scenario.id}
7
+ if test_result
8
+ test_status = test_result.status
9
+ else
10
+ test_status = TestStatus.new
11
+ test_status.missing!
12
+ end
13
+ ReportDatum.new(scenario_id: scenario.id, scenario_name: scenario.name, status: test_status)
14
+ end
15
+ Report.new(data: report_data, topic: topic)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ module SpecTracker
2
+ module StatusUI
3
+ SUCCESS_EMOJI = Emoji.find_by_alias("white_check_mark").raw.freeze
4
+ FAILURE_EMOJI = Emoji.find_by_alias("x").raw.freeze
5
+ SKIPPED_EMOJI = Emoji.find_by_alias("fast_forward").raw.freeze
6
+ MISSING_EMOJI = Emoji.find_by_alias("no_mouth").raw.freeze
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ module Terminal
4
+ class Reporter < BaseReporter
5
+ def initialize
6
+ @ui_mapper = UIMapper.new
7
+ end
8
+
9
+ def print(report)
10
+ terminal_table = ::Terminal::Table.new do |t|
11
+ t.title = report.topic
12
+ t.headings = [
13
+ SpecTracker.configuration.wording[:scenario_id],
14
+ SpecTracker.configuration.wording[:scenario_name],
15
+ SpecTracker.configuration.wording[:test_result]
16
+ ]
17
+ t.rows = ui_mapper.map(report)
18
+ t.add_separator
19
+ t.add_row [SpecTracker.configuration.wording[:progression], nil, "#{report.progression}%"]
20
+ end
21
+ terminal_table.align_column(2, :center)
22
+ terminal_table.align_column(0, :center)
23
+ puts terminal_table
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module SpecTracker
2
+ module Reporter
3
+ module Terminal
4
+ class UIMapper < BaseUIMapper
5
+ def map(report)
6
+ report.data.map do |report_datum|
7
+ [report_datum.scenario_id, report_datum.scenario_name, status_to_ui(report_datum.status)]
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ module SpecTracker
2
+ module SpecParser
3
+ class BaseParser
4
+ def parse(path)
5
+ specifications = []
6
+ if spec_path(path).directory?
7
+ spec_path(path).each_entry do |entry|
8
+ next unless entry.extname == spec_file_extension
9
+ scenarios = parse_single(spec_path(path).join(entry.basename))
10
+ specifications << Specification.new(topic: entry.basename, scenarios: scenarios)
11
+ end
12
+ else
13
+ scenarios = parse_single(spec_path(path))
14
+ specifications << Specification.new(topic: spec_path(path).basename, scenarios: scenarios)
15
+ end
16
+ specifications
17
+ end
18
+
19
+ protected
20
+
21
+ def spec_file_extension
22
+ raise Error.new('override me!')
23
+ end
24
+
25
+ def parse_single(spec_file)
26
+ raise Error.new('override me!')
27
+ end
28
+
29
+ private
30
+
31
+ def spec_path(path)
32
+ SpecTracker.configuration.local_path.join(path)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ module SpecTracker
2
+ module SpecParser
3
+ class CSVParser < BaseParser
4
+ private
5
+
6
+ def spec_file_extension
7
+ '.csv'
8
+ end
9
+
10
+ def parse_single(csv_file)
11
+ scenarios = []
12
+ scenario_id_header = SpecTracker.configuration.scenario_id_header
13
+ scenario_name_header = SpecTracker.configuration.scenario_name_header
14
+ CSV.foreach(csv_file, headers: true, skip_blanks: true) do |row|
15
+ next if row[scenario_id_header].nil?
16
+ scenario_id = row[scenario_id_header]
17
+ scenario_name = row[scenario_name_header]
18
+ scenarios << Scenario.new(id: scenario_id, name: scenario_name)
19
+ end
20
+ scenarios
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module SpecTracker
2
+ module SpecParser
3
+ class GherkinParser < BaseParser
4
+ def initialize
5
+ @language_parser = Gherkin::Parser.new
6
+ end
7
+
8
+ private
9
+
10
+ def spec_file_extension
11
+ '.feature'
12
+ end
13
+
14
+ # FIXME: handle large files
15
+ def parse_single(spec_file)
16
+ file_content = File.read(spec_file)
17
+ parse_result = @language_parser.parse(file_content)
18
+ parse_result[:feature][:children].select { |child| child[:type].downcase == :scenario }.map do |scenario|
19
+ scenario_id = scenario[:name].slice(SpecTracker.configuration.scenario_id_regex, 1)
20
+ next if scenario_id.nil?
21
+ scenario_name = scenario[:name].gsub(SpecTracker.configuration.scenario_id_regex, '').strip
22
+ Scenario.new(id: scenario_id, name: scenario_name)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module SpecTracker
2
+ module SpecParser
3
+ class Scenario
4
+ attr_reader :id, :name
5
+
6
+ def initialize(id:, name:)
7
+ @id = id
8
+ @name = name
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module SpecTracker
2
+ module SpecParser
3
+ class Specification
4
+ attr_reader :topic, :scenarios
5
+
6
+ def initialize(topic:, scenarios:)
7
+ @topic = topic
8
+ @scenarios = scenarios
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ module SpecTracker
2
+ module TestReportParser
3
+ class BaseMapper
4
+ def map(test_case)
5
+ name = get_scenario_name(test_case)
6
+ scenario_id = get_scenario_id(name)
7
+ status = get_scenario_status(test_case)
8
+ TestResult.new(scenario_id: scenario_id, status: status)
9
+ end
10
+
11
+ protected
12
+
13
+ def get_scenario_name(_)
14
+ raise Error.new('override me!')
15
+ end
16
+
17
+ def failed?(_)
18
+ raise Error.new('override me!')
19
+ end
20
+
21
+ def success?(_)
22
+ raise Error.new('override me!')
23
+ end
24
+
25
+ def skipped?(_)
26
+ raise Error.new('override me!')
27
+ end
28
+
29
+ private
30
+
31
+ def get_scenario_id(scenario_name)
32
+ scenario_name.slice(scenario_id_regex, 1)
33
+ end
34
+
35
+ def scenario_id_regex
36
+ SpecTracker.configuration.scenario_id_regex
37
+ end
38
+
39
+ def get_scenario_status(testcase)
40
+ test_status = TestStatus.new
41
+ if success?(testcase)
42
+ test_status.success!
43
+ elsif skipped?(testcase)
44
+ test_status.skipped!
45
+ else
46
+ test_status.failure!
47
+ end
48
+ test_status
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ module SpecTracker
2
+ module TestReportParser
3
+ class BaseParser
4
+ def parse(path)
5
+ test_results = []
6
+ if report_path(path).directory?
7
+ report_path(path).each_entry do |entry|
8
+ results = parse_single(report_path(path).join(entry.basename))
9
+ test_results.concat(results) unless results.empty?
10
+ end
11
+ else
12
+ results = parse_single(report_path(path))
13
+ test_results.concat(results) unless results.empty?
14
+ end
15
+ test_results
16
+ end
17
+
18
+ protected
19
+
20
+ def parse_single(file)
21
+ raise Error.new('override me!')
22
+ end
23
+
24
+ def file_extension
25
+ raise Error.new('override me!')
26
+ end
27
+
28
+ def test_cases(_)
29
+ raise Error.new('override me!')
30
+ end
31
+
32
+ def valid_extension?(entry)
33
+ entry.extname == file_extension
34
+ end
35
+
36
+ private
37
+
38
+ def report_path(path)
39
+ SpecTracker.configuration.local_path.join(path)
40
+ end
41
+ end
42
+ end
43
+ end