faccts 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.
- checksums.yaml +7 -0
- data/Rakefile +12 -0
- data/exe/faccts +7 -0
- data/lib/faccts/01_loading_test_specification/file_reader.rb +31 -0
- data/lib/faccts/02_parsing_test_specification/yaml_assertion_specification_parser.rb +88 -0
- data/lib/faccts/02_parsing_test_specification/yaml_test_assertion_parser.rb +36 -0
- data/lib/faccts/02_parsing_test_specification/yaml_test_parser.rb +120 -0
- data/lib/faccts/03_generating_code/rust_code_generator.rb +96 -0
- data/lib/faccts/03_generating_code/rust_name_to_function_call_converter.rb +73 -0
- data/lib/faccts/03_generating_code/rust_test_assertion_code_generator.rb +41 -0
- data/lib/faccts/04_exporting_results/file_writer.rb +35 -0
- data/lib/faccts/05_reporting_feedback/basic_console_output.rb +14 -0
- data/lib/faccts/entities/test_assertion.rb +16 -0
- data/lib/faccts/integration/acceptance_tester.rb +75 -0
- data/lib/faccts/integration/basic_acceptance_tester.rb +47 -0
- data/lib/faccts/version.rb +5 -0
- data/lib/faccts.rb +7 -0
- metadata +55 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4ab5b68cc81737ad2a9de3e64778e6a17f3174e70ef2b361c1deeb8749c6d4ae
|
|
4
|
+
data.tar.gz: 2b9568a2176536a7f4121304d8009ba76590f105ca12e7f99eef6c5d12d81fcb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 664e60d3cd0143b32705fbe1851dd9a5bc8a45b6662cd4b14bc36720e675390cbc1a066d01e61c0032ed456d28567daf4f1faec3c6ca7f94089bcfe76b28ce61
|
|
7
|
+
data.tar.gz: 813a429fd451bcaeb540dd2daf36c07a559ba3dc006fc6832be919bfc583497bf5dc9d5561f0c940221b947b183523071147e4998055f4f6a55f7856459be195
|
data/Rakefile
ADDED
data/exe/faccts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class YAMLFileReader
|
|
5
|
+
TARGET_FILE_PATH = "./acceptance_tests.yaml"
|
|
6
|
+
|
|
7
|
+
def read_file!
|
|
8
|
+
raise FileNotFoundError unless file_exists?
|
|
9
|
+
|
|
10
|
+
target_file_contents
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class FileNotFoundError < ::StandardError
|
|
14
|
+
ERROR_MESSAGE = "Cannot find acceptance test specification file '#{TARGET_FILE_PATH}'".freeze
|
|
15
|
+
|
|
16
|
+
def initialize(_msg = "")
|
|
17
|
+
super(ERROR_MESSAGE)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def file_exists?
|
|
24
|
+
File.exist?(TARGET_FILE_PATH)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def target_file_contents
|
|
28
|
+
File.read(TARGET_FILE_PATH)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class YAMLTestParser
|
|
5
|
+
class YAMLTestAssertionParser
|
|
6
|
+
class AssertionSpecificationParser
|
|
7
|
+
def parse(assertion_name_specification)
|
|
8
|
+
assertion, remaining_specification = extract_assertion_up_to_first_argument(assertion_name_specification)
|
|
9
|
+
|
|
10
|
+
return assertion if remaining_specification.nil?
|
|
11
|
+
|
|
12
|
+
assertion.combined_with(parse(remaining_specification))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def extract_assertion_up_to_first_argument(assert_specification)
|
|
16
|
+
@specification = assert_specification
|
|
17
|
+
|
|
18
|
+
return no_argument_assertion unless specification_contains_argument?
|
|
19
|
+
|
|
20
|
+
[extracted_assertion_up_to_first_argument, unextracted_specification]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :specification
|
|
26
|
+
|
|
27
|
+
def no_argument_assertion
|
|
28
|
+
assertion = Faccts::TestAssertion.new
|
|
29
|
+
|
|
30
|
+
assertion.action_name = specification
|
|
31
|
+
assertion.arguments = []
|
|
32
|
+
|
|
33
|
+
assertion
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def specification_contains_argument?
|
|
37
|
+
spec_contains_an_open_and_close_wrapper_character?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def spec_contains_an_open_and_close_wrapper_character?
|
|
41
|
+
specification.length > 1 && specification.count(ARGUMENT_WRAPPER_CHAR) > 1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extracted_assertion_up_to_first_argument
|
|
45
|
+
assertion = Faccts::TestAssertion.new
|
|
46
|
+
|
|
47
|
+
assertion.action_name = specification_before_open_wrapper_char + ARGUMENT_NAME_PLACEHOLDER
|
|
48
|
+
assertion.arguments = [value_between_open_and_close_wrapper_chars]
|
|
49
|
+
|
|
50
|
+
assertion
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def specification_before_open_wrapper_char
|
|
54
|
+
specification[...open_wrapper_index]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def open_wrapper_index
|
|
58
|
+
specification.index(ARGUMENT_WRAPPER_CHAR)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def value_between_open_and_close_wrapper_chars
|
|
62
|
+
specification[index_after_open_wrapper...close_wrapper_index]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def index_after_open_wrapper
|
|
66
|
+
open_wrapper_index + 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def close_wrapper_index
|
|
70
|
+
specification_after_open_wrapper = specification[index_after_open_wrapper..]
|
|
71
|
+
|
|
72
|
+
index_after_open_wrapper \
|
|
73
|
+
+ specification_after_open_wrapper.index(ARGUMENT_WRAPPER_CHAR)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def unextracted_specification
|
|
77
|
+
specification_after_first_argument
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def specification_after_first_argument
|
|
81
|
+
index_after_close_wrapper = close_wrapper_index + 1
|
|
82
|
+
|
|
83
|
+
specification[index_after_close_wrapper..]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class YAMLTestParser
|
|
5
|
+
class YAMLTestAssertionParser
|
|
6
|
+
ARGUMENT_WRAPPER_CHAR = "\""
|
|
7
|
+
ARGUMENT_NAME_PLACEHOLDER = "X"
|
|
8
|
+
ASSERTION_NODE_NAME = "assert"
|
|
9
|
+
|
|
10
|
+
def initialize(assertion_specification_parsing_strategy)
|
|
11
|
+
@assertion_specification_parser = assertion_specification_parsing_strategy
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse(assertion_specification)
|
|
15
|
+
@assertion_specification = assertion_specification
|
|
16
|
+
|
|
17
|
+
generated_assertion = assertion_specification_parser.parse(assertion_name_specification)
|
|
18
|
+
generated_assertion.name = test_name
|
|
19
|
+
|
|
20
|
+
generated_assertion
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :assertion_specification_parser, :assertion_specification
|
|
26
|
+
|
|
27
|
+
def assertion_name_specification
|
|
28
|
+
assertion_specification[test_name][ASSERTION_NODE_NAME]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_name
|
|
32
|
+
assertion_specification.keys[0]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Faccts
|
|
6
|
+
class YAMLTestParser
|
|
7
|
+
TEST_SUITE_ROOT_NODE_NAME = "tests"
|
|
8
|
+
ASSERTION_NODE_NAME = "assert"
|
|
9
|
+
|
|
10
|
+
def initialize(test_assertion_parser_strategy)
|
|
11
|
+
@parser = StandardYAMLParser.new
|
|
12
|
+
@test_assertion_extractor = TestAssertionExtractor.new(test_assertion_parser_strategy)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_tests!(test_specification_yaml)
|
|
16
|
+
@test_specification = parser.parse_tests!(test_specification_yaml)
|
|
17
|
+
|
|
18
|
+
validate_specification!
|
|
19
|
+
|
|
20
|
+
assertions = test_assertions_from_specification
|
|
21
|
+
|
|
22
|
+
raise NoTestsGivenError if assertions.empty?
|
|
23
|
+
|
|
24
|
+
assertions
|
|
25
|
+
rescue ::Psych::Exception => e
|
|
26
|
+
raise InvalidYAMLError, e.message
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class NoTestsGivenError < ::StandardError
|
|
30
|
+
ERROR_MESSAGE = "Cannot generate acceptance tests - no tests have been specified!"
|
|
31
|
+
|
|
32
|
+
def initialize(assertion_name = "")
|
|
33
|
+
super(ERROR_MESSAGE % assertion_name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class InvalidYAMLError < ::StandardError
|
|
38
|
+
ERROR_MESSAGE = "Cannot parse acceptance tests file"
|
|
39
|
+
ERROR_TEMPLATE = "#{ERROR_MESSAGE} - %s".freeze
|
|
40
|
+
|
|
41
|
+
def initialize(parse_failure_message = "")
|
|
42
|
+
if parse_failure_message == ""
|
|
43
|
+
super(ERROR_MESSAGE)
|
|
44
|
+
else
|
|
45
|
+
super(ERROR_TEMPLATE % parse_failure_message)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
attr_reader :parser, :test_specification, :test_assertion_extractor
|
|
53
|
+
|
|
54
|
+
def validate_specification!
|
|
55
|
+
raise NoTestsGivenError if test_specification.nil?
|
|
56
|
+
raise NoTestsGivenError if tests.nil?
|
|
57
|
+
raise NoTestsGivenError unless tests.respond_to? :keys
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_assertions_from_specification
|
|
61
|
+
all_test_names
|
|
62
|
+
.map { |test_name| test_assertion_extractor.from_test_name(tests, test_name) }
|
|
63
|
+
.compact
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def all_test_names
|
|
67
|
+
tests.keys
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def tests
|
|
71
|
+
test_specification[TEST_SUITE_ROOT_NODE_NAME]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class TestAssertionExtractor
|
|
75
|
+
def initialize(test_assertion_parser_strategy)
|
|
76
|
+
@test_assertion_parser = test_assertion_parser_strategy
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def from_test_name(tests, test_name)
|
|
80
|
+
@tests = tests
|
|
81
|
+
@test_name = test_name
|
|
82
|
+
|
|
83
|
+
return unless test_contains_assertion?
|
|
84
|
+
return if assertion_is_empty?
|
|
85
|
+
|
|
86
|
+
test_assertion_parser.parse(test_node)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_contains_assertion?
|
|
90
|
+
test_assertion_node.respond_to?(:keys) && test_assertion_node.keys.include?(ASSERTION_NODE_NAME)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_assertion_node
|
|
94
|
+
tests[test_name]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def assertion_is_empty?
|
|
98
|
+
name_from_assertion_node.nil?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_node
|
|
102
|
+
{ test_name => test_assertion_node }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def name_from_assertion_node
|
|
106
|
+
tests[test_name][ASSERTION_NODE_NAME]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
attr_reader :test_assertion_parser, :tests, :test_name
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class StandardYAMLParser
|
|
115
|
+
def parse_tests!(test_specification)
|
|
116
|
+
YAML.load(test_specification)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class RustCodeGenerator
|
|
5
|
+
NO_CODE = ""
|
|
6
|
+
|
|
7
|
+
def initialize(test_assertion_generator_strategy, fixed_template_fetcher_strategy)
|
|
8
|
+
@test_assertion_code_generator = test_assertion_generator_strategy
|
|
9
|
+
@fixed_code_template = RustCodeTemplate.new(fixed_template_fetcher_strategy)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate_from_test_suite!(test_specification_suite)
|
|
13
|
+
@test_specification_suite = test_specification_suite
|
|
14
|
+
|
|
15
|
+
return NO_CODE if test_specification_suite.empty?
|
|
16
|
+
|
|
17
|
+
wrapped_in_fixed_code_template(code_from_test_specification_suite)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class RustCodeTemplate
|
|
21
|
+
RUST_TEMPLATE_INSERT_SYMBOL = "/*{}*/"
|
|
22
|
+
|
|
23
|
+
def initialize(fetcher_strategy)
|
|
24
|
+
@code_template_fetcher = fetcher_strategy
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def generate_with_values(*values)
|
|
28
|
+
values.reduce(code_template) do |template, value|
|
|
29
|
+
insert_value_into_placeholder(template, value)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
attr_reader :code_template_fetcher
|
|
36
|
+
|
|
37
|
+
def code_template
|
|
38
|
+
code_template_fetcher.code_template
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def insert_value_into_placeholder(template, value)
|
|
42
|
+
template.sub(RUST_TEMPLATE_INSERT_SYMBOL, value)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class BasicRustTestAssertionCallTemplateFetcher
|
|
47
|
+
def code_template
|
|
48
|
+
+ " name = \"/*{}*/\";\n" \
|
|
49
|
+
+ " result = /*{}*/;\n" \
|
|
50
|
+
+ " result_output = if result == true { \"PASS\" } else { \"FAIL\" };\n" \
|
|
51
|
+
+ " test_result_output = format!(\"{},{}\", name, result_output);\n" \
|
|
52
|
+
+ " test_suite_result_output += &test_result_output;\n" \
|
|
53
|
+
+ " test_suite_result_output += \"\\n\";\n"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class BasicRustMainProgramTemplateFetcher
|
|
58
|
+
def code_template
|
|
59
|
+
"use std::fs;\n" \
|
|
60
|
+
+ "mod fixture;\n" \
|
|
61
|
+
+ "\n" \
|
|
62
|
+
+ "use crate::fixture::*;\n" \
|
|
63
|
+
+ "\n" \
|
|
64
|
+
+ "fn main() {\n" \
|
|
65
|
+
+ " let mut test_suite_result_output = String::from(\"name,result\\n\");\n" \
|
|
66
|
+
+ " let mut name: &str;\n" \
|
|
67
|
+
+ " let mut result: bool;\n" \
|
|
68
|
+
+ " let mut result_output: &str;\n" \
|
|
69
|
+
+ " let mut test_result_output: String;\n" \
|
|
70
|
+
+ "\n" \
|
|
71
|
+
+ "/*{}*/" \
|
|
72
|
+
+ "\n" \
|
|
73
|
+
+ " let _ = fs::write(\"./results.faccts\", test_suite_result_output);\n" \
|
|
74
|
+
+ "}\n"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
attr_reader :test_assertion_code_generator, :fixed_code_template, :test_specification_suite
|
|
81
|
+
|
|
82
|
+
def wrapped_in_fixed_code_template(value)
|
|
83
|
+
fixed_code_template.generate_with_values(value)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def code_from_test_specification_suite
|
|
87
|
+
generated_test_assertion_list.join
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def generated_test_assertion_list
|
|
91
|
+
test_specification_suite.map do |assert_specification|
|
|
92
|
+
test_assertion_code_generator.generate!(assert_specification)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class RustCodeGenerator
|
|
5
|
+
class RustNameToFunctionCallConverter
|
|
6
|
+
def initialize
|
|
7
|
+
@character_converter = RustCharacterConverter.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def convert!(test_assertion)
|
|
11
|
+
@action_name = test_assertion.action_name
|
|
12
|
+
@arguments = test_assertion.arguments || []
|
|
13
|
+
|
|
14
|
+
converted_name = converted_characters(stripped_action_name)
|
|
15
|
+
|
|
16
|
+
raise InvalidNameError if all_characters_were_removed?(converted_name)
|
|
17
|
+
|
|
18
|
+
"#{converted_name}(#{converted_arguments})"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class InvalidNameError < ::StandardError; end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :character_converter, :action_name, :arguments
|
|
26
|
+
|
|
27
|
+
def stripped_action_name
|
|
28
|
+
action_name.strip
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def converted_characters(name)
|
|
32
|
+
name.chars.map { |char| character_converter.convert!(char) }.join
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def all_characters_were_removed?(converted_name)
|
|
36
|
+
converted_name == ""
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def converted_arguments
|
|
40
|
+
return "" unless arguments.length.positive?
|
|
41
|
+
|
|
42
|
+
arguments.map { |a| "\"#{a}\"" }.join(", ")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RustCharacterConverter
|
|
46
|
+
WHITESPACE_CHARACTERS = " \n\t"
|
|
47
|
+
ALPHANUMERIC_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"
|
|
48
|
+
SUPPORTED_CHARACTERS = "#{ALPHANUMERIC_CHARACTERS}#{WHITESPACE_CHARACTERS}".freeze
|
|
49
|
+
|
|
50
|
+
def convert!(name_character)
|
|
51
|
+
@input_character = name_character
|
|
52
|
+
|
|
53
|
+
return "" unless supported_character?
|
|
54
|
+
return "_" if whitespace?
|
|
55
|
+
|
|
56
|
+
name_character
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_reader :input_character
|
|
62
|
+
|
|
63
|
+
def supported_character?
|
|
64
|
+
SUPPORTED_CHARACTERS.include?(input_character)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def whitespace?
|
|
68
|
+
WHITESPACE_CHARACTERS.include?(input_character)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class RustCodeGenerator
|
|
5
|
+
class RustTestAssertionCodeGenerator
|
|
6
|
+
def initialize(function_call_converter_strategy, template_fetcher_strategy)
|
|
7
|
+
@function_call_converter = function_call_converter_strategy
|
|
8
|
+
@code_template = RustCodeTemplate.new(template_fetcher_strategy)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def generate!(test_assertion)
|
|
12
|
+
@test_assertion = test_assertion
|
|
13
|
+
|
|
14
|
+
code_template.generate_with_values(*injectable_assertion_values)
|
|
15
|
+
rescue RustNameToFunctionCallConverter::InvalidNameError
|
|
16
|
+
raise UnnameableAssertionError, test_assertion.action_name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class UnnameableAssertionError < ::StandardError
|
|
20
|
+
ERROR_TEMPLATE = "Cannot interpret '%s' as a test phase command\n" \
|
|
21
|
+
"Please ensure your test phases include alphanumeric characters!"
|
|
22
|
+
|
|
23
|
+
def initialize(assertion_name = "")
|
|
24
|
+
super(ERROR_TEMPLATE % assertion_name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def injectable_assertion_values
|
|
31
|
+
[test_assertion.name, test_assertion_as_rust_function].compact
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_assertion_as_rust_function
|
|
35
|
+
function_call_converter.convert!(test_assertion)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :function_call_converter, :test_assertion, :code_template, :test_assertion_code_generator
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class RustFileWriter
|
|
5
|
+
TARGET_FILE_PATH = "./src/main.rs"
|
|
6
|
+
|
|
7
|
+
def write_file!(code_to_write)
|
|
8
|
+
raise FileAlreadyExistsError if file_already_exists?
|
|
9
|
+
|
|
10
|
+
@file_contents = code_to_write
|
|
11
|
+
write_file
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class FileAlreadyExistsError < ::StandardError
|
|
15
|
+
ERROR_MESSAGE = "Target generation file '#{TARGET_FILE_PATH}'" \
|
|
16
|
+
+ " already exists.\nI don't want to overwrite it!"
|
|
17
|
+
|
|
18
|
+
def initialize(_msg = "")
|
|
19
|
+
super(ERROR_MESSAGE)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :file_contents
|
|
26
|
+
|
|
27
|
+
def write_file
|
|
28
|
+
File.write(TARGET_FILE_PATH, file_contents)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def file_already_exists?
|
|
32
|
+
File.exist?(TARGET_FILE_PATH)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class BasicConsoleOutput
|
|
5
|
+
PROCESS_COMPLETED_MESSAGE = "Acceptance tests successfully written!"
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
yield
|
|
9
|
+
puts PROCESS_COMPLETED_MESSAGE
|
|
10
|
+
rescue StandardError => e
|
|
11
|
+
puts e.message
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class TestAssertion
|
|
5
|
+
attr_accessor :name, :action_name, :arguments
|
|
6
|
+
|
|
7
|
+
def combined_with(other)
|
|
8
|
+
combined_assertion = TestAssertion.new
|
|
9
|
+
|
|
10
|
+
combined_assertion.action_name = action_name + other.action_name
|
|
11
|
+
combined_assertion.arguments = arguments + other.arguments
|
|
12
|
+
|
|
13
|
+
combined_assertion
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class AcceptanceTester
|
|
5
|
+
def run!
|
|
6
|
+
assert_strategies_are_loaded!
|
|
7
|
+
|
|
8
|
+
report_feedback do
|
|
9
|
+
export(
|
|
10
|
+
code_generated_from(
|
|
11
|
+
test_suite_data_parsed_from(
|
|
12
|
+
test_specification
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class MissingStrategyError < ::StandardError
|
|
20
|
+
ERROR_TEMPLATE = "Could not find %s strategy! Please contact your administrator"
|
|
21
|
+
|
|
22
|
+
def initialize(msg = "")
|
|
23
|
+
super(ERROR_TEMPLATE % msg)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class NoTestSpecificationLoadStrategyError < MissingStrategyError
|
|
28
|
+
def initialize
|
|
29
|
+
super("test loading")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class NoParsingStrategyError < MissingStrategyError
|
|
34
|
+
def initialize
|
|
35
|
+
super("test parsing")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class NoCodeGenerationStrategyError < MissingStrategyError
|
|
40
|
+
def initialize
|
|
41
|
+
super("code generation")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class NoTestExportStrategyError < MissingStrategyError
|
|
46
|
+
def initialize
|
|
47
|
+
super("test exporting")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class NoFeedbackOutputStrategyError < MissingStrategyError
|
|
52
|
+
def initialize
|
|
53
|
+
super("feedback output")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def assert_strategies_are_loaded!
|
|
60
|
+
required_strategies_table.each do |action, missing_action_error|
|
|
61
|
+
raise missing_action_error unless respond_to? action
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def required_strategies_table
|
|
66
|
+
{
|
|
67
|
+
test_specification: NoTestSpecificationLoadStrategyError,
|
|
68
|
+
test_suite_data_parsed_from: NoParsingStrategyError,
|
|
69
|
+
code_generated_from: NoCodeGenerationStrategyError,
|
|
70
|
+
export: NoTestExportStrategyError,
|
|
71
|
+
report_feedback: NoFeedbackOutputStrategyError
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Faccts
|
|
4
|
+
class BasicAcceptanceTester < AcceptanceTester
|
|
5
|
+
def initialize
|
|
6
|
+
@file_reader = YAMLFileReader.new
|
|
7
|
+
@spec_parser = YAMLTestParser.new(
|
|
8
|
+
YAMLTestParser::YAMLTestAssertionParser.new(
|
|
9
|
+
YAMLTestParser::YAMLTestAssertionParser::AssertionSpecificationParser.new
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
@code_generator = RustCodeGenerator.new(
|
|
13
|
+
RustCodeGenerator::RustTestAssertionCodeGenerator.new(
|
|
14
|
+
RustCodeGenerator::RustNameToFunctionCallConverter.new,
|
|
15
|
+
RustCodeGenerator::BasicRustTestAssertionCallTemplateFetcher.new
|
|
16
|
+
),
|
|
17
|
+
RustCodeGenerator::BasicRustMainProgramTemplateFetcher.new
|
|
18
|
+
)
|
|
19
|
+
@file_writer = RustFileWriter.new
|
|
20
|
+
@feedback_reporter = BasicConsoleOutput.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_specification
|
|
24
|
+
file_reader.read_file!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_suite_data_parsed_from(specification)
|
|
28
|
+
spec_parser.create_tests!(specification)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def code_generated_from(test_suite)
|
|
32
|
+
code_generator.generate_from_test_suite!(test_suite)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def export(test_code)
|
|
36
|
+
file_writer.write_file!(test_code)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def report_feedback(&)
|
|
40
|
+
feedback_reporter.run(&)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
attr_reader :file_reader, :spec_parser, :code_generator, :file_writer, :feedback_reporter
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/faccts.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: faccts
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- bull313
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Execute automated acceptance tests for your project
|
|
13
|
+
executables:
|
|
14
|
+
- faccts
|
|
15
|
+
extensions: []
|
|
16
|
+
extra_rdoc_files: []
|
|
17
|
+
files:
|
|
18
|
+
- Rakefile
|
|
19
|
+
- exe/faccts
|
|
20
|
+
- lib/faccts.rb
|
|
21
|
+
- lib/faccts/01_loading_test_specification/file_reader.rb
|
|
22
|
+
- lib/faccts/02_parsing_test_specification/yaml_assertion_specification_parser.rb
|
|
23
|
+
- lib/faccts/02_parsing_test_specification/yaml_test_assertion_parser.rb
|
|
24
|
+
- lib/faccts/02_parsing_test_specification/yaml_test_parser.rb
|
|
25
|
+
- lib/faccts/03_generating_code/rust_code_generator.rb
|
|
26
|
+
- lib/faccts/03_generating_code/rust_name_to_function_call_converter.rb
|
|
27
|
+
- lib/faccts/03_generating_code/rust_test_assertion_code_generator.rb
|
|
28
|
+
- lib/faccts/04_exporting_results/file_writer.rb
|
|
29
|
+
- lib/faccts/05_reporting_feedback/basic_console_output.rb
|
|
30
|
+
- lib/faccts/entities/test_assertion.rb
|
|
31
|
+
- lib/faccts/integration/acceptance_tester.rb
|
|
32
|
+
- lib/faccts/integration/basic_acceptance_tester.rb
|
|
33
|
+
- lib/faccts/version.rb
|
|
34
|
+
homepage: https://github.com/bull313/faccts
|
|
35
|
+
licenses: []
|
|
36
|
+
metadata:
|
|
37
|
+
rubygems_mfa_required: 'true'
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 3.1.0
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.6.9
|
|
53
|
+
specification_version: 4
|
|
54
|
+
summary: FAccTs - Framework for Acceptance Tests
|
|
55
|
+
test_files: []
|