strut 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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