strut 0.1.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.
@@ -0,0 +1,25 @@
1
+ module Strut
2
+ Interaction = Struct.new(:uri, :method, :statusCode)
3
+
4
+ class InteractionFactory
5
+ def make_interaction(path_stack)
6
+ interaction = Interaction.new
7
+
8
+ if path_stack[0] == "paths"
9
+ interaction.uri = path_stack[1] unless extension?(path_stack[1])
10
+ end
11
+
12
+ interaction.method = path_stack[2] unless extension?(path_stack[2])
13
+
14
+ if path_stack[3] == "responses"
15
+ interaction.statusCode = path_stack[4] unless extension?(path_stack[4])
16
+ end
17
+
18
+ interaction
19
+ end
20
+
21
+ def extension?(tag)
22
+ tag =~ /^x-/
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,57 @@
1
+ require "strut/extensions"
2
+ require "strut/document_builder"
3
+ require "strut/interaction_factory"
4
+ require "strut/scenario_builder"
5
+ require "strut/slim_command"
6
+ require "strut/slim_command_factory"
7
+
8
+ module Strut
9
+ class Parser
10
+ X_SCENARIO_PREFIX = "x-scenario-"
11
+
12
+ def initialize(namespace)
13
+ @namespace = namespace
14
+ @command_factory = SlimCommandFactory.new
15
+ @document_builder = DocumentBuilder.new
16
+ @interaction_factory = InteractionFactory.new
17
+ @scenario_builder = ScenarioBuilder.new(@document_builder, @command_factory)
18
+ @scenario_number = 1
19
+ end
20
+
21
+ def parse(yaml)
22
+ parsed_yaml = Psych::parse_yaml(yaml)
23
+ append_import_command
24
+ extract_scenarios(parsed_yaml)
25
+ @document_builder.document
26
+ end
27
+
28
+ def append_import_command
29
+ metadata = CommandMetadata.new(1)
30
+ import_command = @command_factory.make_import_command(metadata, @namespace)
31
+ @document_builder.append_command(import_command)
32
+ end
33
+
34
+ def extract_scenarios(node)
35
+ wrapped_node = {"value" => node, "line" => 0}
36
+ extract_scenarios_for_node("", wrapped_node, [])
37
+ end
38
+
39
+ def extract_scenarios_for_node(node_name, node, path_stack)
40
+ if node_name.start_with?(X_SCENARIO_PREFIX)
41
+ fixture = node_name.gsub(/^#{X_SCENARIO_PREFIX}/, "")
42
+ interaction = @interaction_factory.make_interaction(path_stack)
43
+ @scenario_number = @scenario_builder.extract_scenarios_for_interaction(@scenario_number, interaction, fixture, node)
44
+ else
45
+ extract_scenarios_for_children(@scenario_number, node["value"], path_stack)
46
+ end
47
+ end
48
+
49
+ def extract_scenarios_for_children(scenario_number, node, path_stack)
50
+ return unless node.respond_to?(:each_pair)
51
+ node.each_pair do |child_node_name, child_node|
52
+ next_path_stack = path_stack + [child_node_name]
53
+ extract_scenarios_for_node(child_node_name.to_s, child_node, next_path_stack)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ require "strut/scenario_result"
2
+
3
+ module Strut
4
+ class Report
5
+ attr_reader :number_scenarios, :number_passed, :number_failed, :number_skipped
6
+ attr_reader :scenario_results, :errors
7
+
8
+ def initialize
9
+ @scenario_results = []
10
+ @errors = []
11
+ end
12
+
13
+ def add_scenario_result(result)
14
+ @scenario_results << result
15
+ end
16
+
17
+ def add_error(error)
18
+ @errors << error
19
+ end
20
+
21
+ def annotations_for_line(line)
22
+ all_annotations = @scenario_results.map do |result|
23
+ result.annotations_for_line(line)
24
+ end
25
+ all_annotations.reject { |a| a.empty? }.flatten
26
+ end
27
+
28
+ def number_scenarios
29
+ @scenario_results.count
30
+ end
31
+
32
+ def number_passed
33
+ number_with_result(SCENARIO_PASS)
34
+ end
35
+
36
+ def number_failed
37
+ number_with_result(SCENARIO_FAIL)
38
+ end
39
+
40
+ def number_skipped
41
+ number_with_result(SCENARIO_ERROR)
42
+ end
43
+
44
+ def number_with_result(result)
45
+ @scenario_results.select { |scenario| scenario.result == result }.count
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ require "strut/report"
2
+
3
+ module Strut
4
+ class ReportBuilder
5
+ def build(responses, document)
6
+ report = Report.new
7
+
8
+ scenario_results = Hash.new { |h, k| h[k] = ScenarioResult.new }
9
+ responses.each do |response|
10
+ handle_response(response, document, report, scenario_results)
11
+ end
12
+
13
+ scenario_results.each_pair do |_, result|
14
+ report.add_scenario_result(result)
15
+ end
16
+
17
+ report
18
+ end
19
+
20
+ def handle_response(response, document, report, scenario_results)
21
+ command_id, command_result = *response
22
+ if command_id == "error"
23
+ report.add_error(command_result.to_s)
24
+ else
25
+ metadata = document.metadata_for_command_id(command_id)
26
+ if metadata
27
+ process_result(command_result, metadata, report, scenario_results)
28
+ else
29
+ report.add_error("Unexpected response from Slim: #{response.inspect}")
30
+ end
31
+ end
32
+ end
33
+
34
+ def process_result(result, metadata, report, scenario_results)
35
+ scenario_result = scenario_results[metadata.scenario_number]
36
+ scenario_result.name = "Scenario #{metadata.scenario_number}, line #{metadata.line}"
37
+
38
+ if exception_message = exceptional_result?(result)
39
+ scenario_result.add_exception_for_line(metadata.line, exception_message)
40
+ elsif failed_result?(result, metadata)
41
+ fail_message = "Expected #{metadata.expected_value} but got #{result}"
42
+ scenario_result.add_fail_for_line(metadata.line, fail_message)
43
+ else
44
+ scenario_result.add_ok_for_line(metadata.line)
45
+ end
46
+ end
47
+
48
+ def exceptional_result?(result)
49
+ result =~ /^__EXCEPTION__:message:(.+)/ ? $1 : nil
50
+ end
51
+
52
+ def failed_result?(result, metadata)
53
+ metadata.expected_value && metadata.expected_value.to_s != result.to_s
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,47 @@
1
+ module Strut
2
+ class ReportJUnitFormatter
3
+ def format(report)
4
+ doc = REXML::Document.new("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
5
+ results = report.scenario_results
6
+ add_testsuite(results, doc)
7
+ doc.to_s
8
+ end
9
+
10
+ def add_testsuite(results, doc)
11
+ testsuite = doc.add_element("testsuite")
12
+ results.each do |scenario|
13
+ add_testcase_for_scenario(scenario, testsuite)
14
+ end
15
+ end
16
+
17
+ def add_testcase_for_scenario(scenario, testsuite)
18
+ testcase = testsuite.add_element("testcase")
19
+ testcase.attributes["name"] = scenario.name
20
+ testcase.attributes["time"] = scenario.time
21
+ testcase.attributes["classname"] = "swagger"
22
+ add_result_for_testcase(scenario, testcase)
23
+ end
24
+
25
+ def add_result_for_testcase(scenario, testcase)
26
+ if scenario.result == SCENARIO_FAIL
27
+ add_failure_for_testcase(scenario.message, testcase)
28
+ elsif scenario.result == SCENARIO_ERROR
29
+ add_error_for_testcase(scenario.message, testcase)
30
+ end
31
+ end
32
+
33
+ def add_failure_for_testcase(message, testcase)
34
+ add_element_for_testcase("failure", testcase, message, "assert")
35
+ end
36
+
37
+ def add_error_for_testcase(message, testcase)
38
+ add_element_for_testcase("error", testcase, message, "exception")
39
+ end
40
+
41
+ def add_element_for_testcase(element, testcase, message, type)
42
+ node = testcase.add_element(element)
43
+ node.attributes["message"] = message
44
+ node.attributes["type"] = type
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,91 @@
1
+ require "rubygems"
2
+ require "term/ansicolor"
3
+ include Term::ANSIColor
4
+ require "strut/report"
5
+
6
+ module Strut
7
+ class ReportPrettyFormatter
8
+ def initialize(lines)
9
+ @lines = lines
10
+ end
11
+
12
+ def format(report)
13
+ begin
14
+ out = StringIO.new
15
+ $stdout = out
16
+ print_report_to_stdout(report)
17
+ ensure
18
+ $stdout = STDOUT
19
+ end
20
+ out.string
21
+ end
22
+
23
+ def print_report_to_stdout(report)
24
+ @lines.each_line.each_with_index do |line, index|
25
+ annotations = report.annotations_for_line(index+1)
26
+ print_line_with_annotations(line.chomp, annotations)
27
+ end
28
+ puts
29
+ print_errors(report)
30
+ print "#{report.number_scenarios} scenarios ("
31
+ print green { "#{report.number_passed} passed" }, ", "
32
+ print red { "#{report.number_failed} failed" }, ", "
33
+ print yellow { "#{report.number_skipped} skipped" }, ")"
34
+ end
35
+
36
+ def print_line_with_annotations(line, annotations)
37
+ if annotations.empty?
38
+ print_unannotated_line(line)
39
+ elsif
40
+ print_annotated_line(annotations, line)
41
+ end
42
+ end
43
+
44
+ def print_unannotated_line(line)
45
+ puts line
46
+ end
47
+
48
+ def print_annotated_line(annotations, line)
49
+ # TODO: print all annotations, not just the first
50
+ annotation = annotations.first
51
+
52
+ case annotation.type
53
+ when ANNOTATION_EXCEPTION
54
+ print_exception_line(line, annotation.message)
55
+ when ANNOTATION_FAIL
56
+ print_fail_line(line, annotation.message)
57
+ else
58
+ print_ok_line(line)
59
+ end
60
+ end
61
+
62
+ def print_exception_line(line, message)
63
+ print_error_line(line, :black, :on_yellow, message, :yellow, :on_black)
64
+ end
65
+
66
+ def print_fail_line(line, message)
67
+ print_error_line(line, :white, :on_red, message, :red, :on_white)
68
+ end
69
+
70
+ def print_ok_line(line)
71
+ print_line(line, :black, :on_green)
72
+ end
73
+
74
+ def print_error_line(line, line_fg, line_bg, message, message_fg, message_bg)
75
+ print_line(line, line_fg, line_bg)
76
+ print_line(message, message_fg, message_bg)
77
+ end
78
+
79
+ def print_line(line, foreground, background)
80
+ puts line.send(foreground).send(background)
81
+ end
82
+
83
+ def print_errors(report)
84
+ return if report.errors.empty?
85
+ report.errors.each do |error|
86
+ print red { error }, "\n"
87
+ end
88
+ puts
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,124 @@
1
+ module Strut
2
+ class ScenarioBuilder
3
+ def initialize(document_builder, command_factory)
4
+ @document_builder = document_builder
5
+ @command_factory = command_factory
6
+ end
7
+
8
+ def extract_scenarios_for_interaction(scenario_number, interaction, fixture, node)
9
+ instance = "instance-#{scenario_number}"
10
+ line = node["line"]
11
+ scenario_definitions_for_node(node).each do |scenario_stages|
12
+ make_scenario(scenario_number, line, instance, fixture, interaction, scenario_stages)
13
+ scenario_number += 1
14
+ end
15
+ scenario_number
16
+ end
17
+
18
+ def scenario_definitions_for_node(node)
19
+ raw_scenarios = node["value"]
20
+ raw_scenarios = [raw_scenarios] if raw_scenarios.respond_to?(:each_pair)
21
+ raw_scenarios
22
+ end
23
+
24
+ def make_scenario(scenario_number, line, instance, fixture, interaction, scenario_stages)
25
+ append_make_command(scenario_number, line, instance, fixture)
26
+ append_uri_command(scenario_number, line, instance, interaction.uri, scenario_stages)
27
+ append_method_command(scenario_number, line, instance, interaction.method)
28
+ append_given_commands(scenario_number, scenario_stages, instance)
29
+ append_execute_command(scenario_number, line, instance)
30
+ append_then_commands(scenario_number, scenario_stages, instance)
31
+ append_status_command(scenario_number, line, instance, interaction.statusCode)
32
+ end
33
+
34
+ def append_make_command(scenario_number, line, instance, class_name)
35
+ metadata = CommandMetadata.new(scenario_number, line)
36
+ make_command = @command_factory.make_make_command(metadata, instance, class_name)
37
+ @document_builder.append_command(make_command)
38
+ end
39
+
40
+ def append_uri_command(scenario_number, line, instance, uri, scenario_stages)
41
+ if uri
42
+ metadata = CommandMetadata.new(scenario_number, line)
43
+ combined_uri = combine_uri_with_parameters(scenario_stages, uri)
44
+ path_command = make_set_command(scenario_number, line, instance, "uri", combined_uri)
45
+ @document_builder.append_command(path_command)
46
+ end
47
+ end
48
+
49
+ def append_method_command(scenario_number, line, instance, method)
50
+ if method
51
+ metadata = CommandMetadata.new(scenario_number, line)
52
+ method_command = make_set_command(scenario_number, line, instance, "method", method)
53
+ @document_builder.append_command(method_command)
54
+ end
55
+ end
56
+
57
+ def combine_uri_with_parameters(stages, uri)
58
+ combined_uri = uri.dup
59
+ when_stages = stages_with_names(stages, ["when"])
60
+ when_stages.each do |stage|
61
+ stage.each do |k, v|
62
+ argument = v["value"].to_s
63
+ combined_uri.gsub!(/\{#{k}\}/, argument)
64
+ end
65
+ end
66
+ combined_uri
67
+ end
68
+
69
+ def append_given_commands(scenario_number, stages, instance)
70
+ given_stages = stages_with_names(stages, ["given", "when"])
71
+ parse_stages(given_stages) { |k, v| make_given_command(scenario_number, k, v, instance) }
72
+ end
73
+
74
+ def append_execute_command(scenario_number, line, instance)
75
+ metadata = CommandMetadata.new(scenario_number, line)
76
+ execute_command = @command_factory.make_call_command(metadata, instance, "execute")
77
+ @document_builder.append_command(execute_command)
78
+ end
79
+
80
+ def append_then_commands(scenario_number, stages, instance)
81
+ then_stages = stages_with_names(stages, ["then"])
82
+ parse_stages(then_stages) { |k, v| make_then_command(scenario_number, k, v, instance) }
83
+ end
84
+
85
+ def append_status_command(scenario_number, line, instance, statusCode)
86
+ if statusCode
87
+ metadata = CommandMetadata.new(scenario_number, line, statusCode)
88
+ status_command = @command_factory.make_call_command(metadata, instance, "statusCode")
89
+ @document_builder.append_command(status_command)
90
+ end
91
+ end
92
+
93
+ def make_given_command(scenario_number, property_name, value_container, instance)
94
+ line = value_container["line"]
95
+ value = value_container["value"]
96
+ make_set_command(scenario_number, line, instance, property_name, value)
97
+ end
98
+
99
+ def make_set_command(scenario_number, line, instance, name, value)
100
+ metadata = CommandMetadata.new(scenario_number, line)
101
+ @command_factory.make_call_command(metadata, instance, "set_#{name}", value)
102
+ end
103
+
104
+ def make_then_command(scenario_number, property_name, value_container, instance)
105
+ line = value_container["line"]
106
+ value = value_container["value"]
107
+ metadata = CommandMetadata.new(scenario_number, line, value)
108
+ @command_factory.make_call_command(metadata, instance, property_name)
109
+ end
110
+
111
+ def stages_with_names(stages, names)
112
+ names.map { |name| stages[name] }.reject { |stage| stage.nil? }.map { |stage| stage["value"] }
113
+ end
114
+
115
+ def parse_stages(stages, &block)
116
+ stages.each do |stage|
117
+ stage.each do |k, v|
118
+ command = block.call(k, v)
119
+ @document_builder.append_command(command)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end