spec_tracker 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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