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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +48 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +79 -0
- data/LICENSE +24 -0
- data/README.md +84 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/spec_tracker +5 -0
- data/lib/spec_tracker.rb +50 -0
- data/lib/spec_tracker/cli.rb +24 -0
- data/lib/spec_tracker/config/configuration.rb +23 -0
- data/lib/spec_tracker/config/en.yml +8 -0
- data/lib/spec_tracker/config/fr.yml +8 -0
- data/lib/spec_tracker/report_task.rb +26 -0
- data/lib/spec_tracker/reporter/base_reporter.rb +24 -0
- data/lib/spec_tracker/reporter/base_ui_mapper.rb +25 -0
- data/lib/spec_tracker/reporter/report.rb +17 -0
- data/lib/spec_tracker/reporter/report_datum.rb +13 -0
- data/lib/spec_tracker/reporter/report_mapper.rb +19 -0
- data/lib/spec_tracker/reporter/status_ui.rb +8 -0
- data/lib/spec_tracker/reporter/terminal/reporter.rb +28 -0
- data/lib/spec_tracker/reporter/terminal/ui_mapper.rb +13 -0
- data/lib/spec_tracker/spec_parser/base_parser.rb +36 -0
- data/lib/spec_tracker/spec_parser/csv_parser.rb +24 -0
- data/lib/spec_tracker/spec_parser/gherkin_parser.rb +27 -0
- data/lib/spec_tracker/spec_parser/scenario.rb +12 -0
- data/lib/spec_tracker/spec_parser/specification.rb +12 -0
- data/lib/spec_tracker/test_report_parser/base_mapper.rb +52 -0
- data/lib/spec_tracker/test_report_parser/base_parser.rb +43 -0
- data/lib/spec_tracker/test_report_parser/j_unit/mapper.rb +25 -0
- data/lib/spec_tracker/test_report_parser/j_unit/parser.rb +31 -0
- data/lib/spec_tracker/test_report_parser/test_result.rb +12 -0
- data/lib/spec_tracker/test_status.rb +46 -0
- data/lib/spec_tracker/version.rb +3 -0
- data/spec_tracker.gemspec +39 -0
- data/specifications/Spec Fonctionnelles SpecTracker - Configuration.csv +11 -0
- data/specifications/Spec Fonctionnelles SpecTracker - Parse specifications.csv +20 -0
- data/specifications/configuration.feature +11 -0
- data/specifications/parse_csv_specifications.feature +11 -0
- data/specifications/parse_gherkin_specifications.feature +11 -0
- 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
|
+
: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,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
|