rcomp 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,169 @@
1
+ Feature: Test
2
+ Users should have the ability to run tests
3
+
4
+ # faulty configuration
5
+ Scenario: Test without command
6
+ When I run `rcomp test`
7
+ Then the output should contain "No command present"
8
+ And the exit status should be 1
9
+
10
+ Scenario: Test without init
11
+ Given I run `rcomp c ./exec`
12
+ When I run `rcomp test`
13
+ Then the output should contain "No RComp directory"
14
+ And the exit status should be 1
15
+
16
+ Scenario: Test with partial init
17
+ Given I run `rcomp c ./exec`
18
+ And a directory named "rcomp"
19
+ And a directory named "rcomp/tests"
20
+ When I run `rcomp test`
21
+ Then the output should contain "Missing RComp directories"
22
+ And the exit status should be 1
23
+
24
+ # no tests
25
+ @basic-conf
26
+ Scenario: Test with no tests
27
+ When I run `rcomp test`
28
+ Then the output should contain "0 tests ()"
29
+ And the exit status should be 0
30
+
31
+ # stdout single test
32
+ @basic-conf
33
+ Scenario: Test with single test passing
34
+ Given a file named "rcomp/tests/test1.test" with:
35
+ """
36
+ ABC
37
+
38
+ """
39
+ And a file named "rcomp/expected/test1.out" with:
40
+ """
41
+ ABC
42
+
43
+ """
44
+ When I run `rcomp test`
45
+ Then the output should contain "1 test (1 passed)"
46
+ And the exit status should be 0
47
+
48
+ @basic-conf
49
+ Scenario: Test with single test skipped
50
+ Given a file named "rcomp/tests/test1.test" with:
51
+ """
52
+ ABC
53
+
54
+ """
55
+ When I run `rcomp test`
56
+ Then the output should contain "1 test (1 skipped)"
57
+ And the exit status should be 0
58
+
59
+ @basic-conf
60
+ Scenario: Test with single test failing
61
+ Given a file named "rcomp/tests/test1.test" with:
62
+ """
63
+ ABC
64
+
65
+ """
66
+ And a file named "rcomp/expected/test1.out" with:
67
+ """
68
+ XYZ
69
+
70
+ """
71
+ When I run `rcomp test`
72
+ Then the output should contain "1 test (1 failed)"
73
+ And the exit status should be 1
74
+
75
+ # stdout multiple tests
76
+ @basic-conf
77
+ @load-assorted-tests
78
+ Scenario: Test multiple
79
+ When I run `rcomp test`
80
+ Then the output should contain "3 tests (1 failed, 1 skipped, 1 passed)"
81
+ And the exit status should be 1
82
+
83
+ # stderr single test
84
+ @err-conf
85
+ Scenario: Test err with single test passing
86
+ Given a file named "rcomp/tests/test1.test" with:
87
+ """
88
+ ABC
89
+
90
+ """
91
+ And a file named "rcomp/expected/test1.err" with:
92
+ """
93
+ ABC
94
+
95
+ """
96
+ When I run `rcomp test`
97
+ Then the output should contain "1 test (1 passed)"
98
+ And the exit status should be 0
99
+
100
+ @err-conf
101
+ Scenario: Test err with single test failing
102
+ Given a file named "rcomp/tests/test1.test" with:
103
+ """
104
+ ABC
105
+
106
+ """
107
+ And a file named "rcomp/expected/test1.err" with:
108
+ """
109
+ XYZ
110
+
111
+ """
112
+ When I run `rcomp test`
113
+ Then the output should contain "1 test (1 failed)"
114
+ And the exit status should be 1
115
+
116
+ # stderr multiple tests
117
+ @err-conf
118
+ @load-assorted-err-tests
119
+ Scenario: Test multiple err: passing, skipped and failing
120
+ When I run `rcomp test`
121
+ Then the output should contain "3 tests (1 failed, 1 skipped, 1 passed)"
122
+ And the exit status should be 1
123
+
124
+ # alias
125
+ @basic-conf
126
+ @load-assorted-tests
127
+ Scenario: Test alias t
128
+ When I run `rcomp t`
129
+ Then the output should contain "3 tests (1 failed, 1 skipped, 1 passed)"
130
+ And the exit status should be 1
131
+
132
+ # filters
133
+ @basic-conf
134
+ @load-assorted-tests
135
+ Scenario: Test with filter: catches all
136
+ When I run `rcomp test --grep=test`
137
+ Then the output should contain "3 tests (1 failed, 1 skipped, 1 passed)"
138
+ And the exit status should be 1
139
+
140
+ @basic-conf
141
+ @load-assorted-tests
142
+ Scenario: Test with filter: catches some
143
+ When I run `rcomp t --grep=dir`
144
+ Then the output should contain "2 tests (1 failed, 1 skipped)"
145
+ And the exit status should be 1
146
+
147
+ @basic-conf
148
+ @load-assorted-tests
149
+ Scenario: Test with filter: catches none
150
+ When I run `rcomp t --grep=xyz`
151
+ Then the output should contain "0 tests ()"
152
+ And the exit status should be 0
153
+
154
+ # custom tests directory
155
+ @custom-conf
156
+ Scenario: Custom conf test
157
+ Given a file named "test/integration/rcomp/tests/test1.test" with:
158
+ """
159
+ ABC
160
+
161
+ """
162
+ And a file named "test/integration/rcomp/expected/test1.out" with:
163
+ """
164
+ ABC
165
+
166
+ """
167
+ When I run `rcomp test`
168
+ Then the output should contain "1 test (1 passed)"
169
+ And the exit status should be 0
@@ -0,0 +1,6 @@
1
+ Feature: Version
2
+ A user should be able to find out RComp's version number
3
+
4
+ Scenario: run version
5
+ When I run `rcomp version`
6
+ Then the output should contain "0.1.0"
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+
3
+ module RComp
4
+ module Actions
5
+ def rm_rf(directory)
6
+ if File.exist? directory
7
+ FileUtils.rm_rf directory
8
+ end
9
+ end
10
+
11
+ def rm(file)
12
+ if File.exist? file
13
+ FileUtils.rm file
14
+ end
15
+ end
16
+
17
+ def mkdir(path)
18
+ unless File.exist? path
19
+ FileUtils.mkdir path
20
+ end
21
+ end
22
+
23
+ def mkpath_to(path)
24
+ unless File.exists? path
25
+ FileUtils.mkpath File.dirname(path)
26
+ end
27
+ end
28
+
29
+ def touch(path)
30
+ unless File.exist? path
31
+ FileUtils.touch path
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/rcomp/cli.rb ADDED
@@ -0,0 +1,124 @@
1
+ require 'thor'
2
+
3
+ module RComp
4
+ class CLI < Thor
5
+
6
+ include RComp::Actions
7
+ include RComp::Runner
8
+ include RComp::Suite
9
+
10
+ def initialize(args=[], options={}, config={})
11
+ super
12
+ # load Conf singleton
13
+ @conf = Conf.instance
14
+ end
15
+
16
+ ##
17
+ ## CLI Commands
18
+ ##
19
+
20
+ # init
21
+ desc "init", "Setup rcomp test directory"
22
+ def init
23
+ if initialized?
24
+ puts "RComp already initialized"
25
+ exit 1
26
+ end
27
+
28
+ unless Dir.exists?(File.dirname(@conf.root))
29
+ puts "No directory #{File.dirname(@conf.root)}"
30
+ exit 1
31
+ end
32
+
33
+ # Create RComp directories
34
+ mkdir @conf.root
35
+ mkdir @conf.test_root
36
+ mkdir @conf.expected_root
37
+ mkdir @conf.result_root
38
+
39
+ puts "RComp successfully initialized"
40
+ end
41
+
42
+ # version
43
+ desc "version", "Prints RComp's version information"
44
+ def version
45
+ puts "RComp version #{RComp::VERSION}"
46
+ end
47
+ map %w(-v --version) => :version
48
+
49
+ # set-command
50
+ desc "set_command COMMAND", "Sets the command RComp will run tests with"
51
+ def set_command(command)
52
+ @conf.set_conf_value("command", command)
53
+ end
54
+ map "c" => :set_command
55
+
56
+ # set-directory
57
+ desc "set_directory PATH", "Set the directory RComp will store files"
58
+ def set_directory(path)
59
+ @conf.set_conf_value("directory", path)
60
+ end
61
+ map "d" => :set_directory
62
+
63
+ # test
64
+ desc "test", "Run all tests"
65
+ method_option :grep,
66
+ :type => :string,
67
+ :desc => "Only test files that match pattern"
68
+ def test
69
+ @conf.require_basic_conf
70
+ if @options[:grep]
71
+ run_suite(load_suite(@options[:grep]), :test)
72
+ else
73
+ run_suite(load_suite, :test)
74
+ end
75
+ end
76
+ map "t" => :test
77
+
78
+ # generate
79
+ desc "generate", "Generate expected output for all tests"
80
+ method_option :grep,
81
+ :type => :string,
82
+ :desc => "Only test files that match pattern"
83
+ method_option :overwrite,
84
+ :type => :boolean,
85
+ :default => false,
86
+ :aliases => "-O",
87
+ :desc => "Overwrite expected output file for test if present"
88
+ def generate
89
+ @conf.require_basic_conf
90
+
91
+ # Display confirmation dialouge when -O is passed without filter
92
+ if !@options[:grep] && options.overwrite
93
+ confirm_action "This will overwrite all existing expected results."
94
+ end
95
+
96
+ if @options[:grep]
97
+ run_suite(load_suite(@options[:grep]), :generate, @options)
98
+ else
99
+ run_suite(load_suite, :generate, @options)
100
+ end
101
+ end
102
+ map "g" => :generate
103
+
104
+ private
105
+
106
+ def confirm_action(warning)
107
+ puts warning
108
+ print 'Are you sure? (Y/N) '
109
+ confirm = STDIN.gets.chomp
110
+
111
+ unless confirm.downcase == 'y'
112
+ say 'Aborting...'
113
+ exit 1
114
+ end
115
+ end
116
+
117
+ def initialized?
118
+ File.exists?(@conf.root) &&
119
+ File.exists?(@conf.test_root) &&
120
+ File.exists?(@conf.result_root) &&
121
+ File.exists?(@conf.expected_root)
122
+ end
123
+ end
124
+ end
data/lib/rcomp/conf.rb ADDED
@@ -0,0 +1,128 @@
1
+ require 'singleton'
2
+ require 'yaml'
3
+
4
+ module RComp
5
+ class Conf
6
+
7
+ include Singleton
8
+ include RComp::Actions
9
+
10
+ attr_reader :root, :test_root, :result_root, :expected_root,
11
+ :command
12
+
13
+ # Initialize a new config object
14
+ #
15
+ # Loads options from config file, merges with defaults
16
+ # and stores everything in memory
17
+ def initialize
18
+ # Config file path
19
+ @path = '.rcomp'
20
+
21
+ # Set valid keys for config file
22
+ @valid_keys = ['directory',
23
+ 'command']
24
+
25
+ # Set default options and overwrite with config file options
26
+ @default = { 'directory' => 'rcomp' }
27
+ @custom = read_conf_file
28
+ @conf = @default.merge(@custom)
29
+
30
+ # Load configuration values into attributes
31
+ @command = @conf['command']
32
+ @root = @conf['directory']
33
+ @test_root = @root + '/tests'
34
+ @result_root = @root + '/results'
35
+ @expected_root = @root + '/expected'
36
+ end
37
+
38
+ # Set a configuration value and write it to the config file
39
+ #
40
+ # Returns nothing
41
+ def set_conf_value(key, value)
42
+ @custom[key] = value
43
+ puts "#{key} set to #{value}"
44
+ write_conf_file
45
+ end
46
+
47
+ # Emit error unless all required conf keys are present in conf file
48
+ #
49
+ # Returns nothing
50
+ def require_basic_conf
51
+ require_command
52
+ require_root_exists
53
+ require_root_subdirs
54
+ end
55
+
56
+
57
+ private
58
+
59
+ # Write the current config options to the config file
60
+ #
61
+ # Returns nothing
62
+ def write_conf_file
63
+ exit 1 unless @path
64
+ touch @path unless File.exists?(@path)
65
+ conf_file = File.open(@path, 'w')
66
+ conf_file.puts YAML.dump @custom
67
+ end
68
+
69
+ # Read the config options from RComp's configuration file
70
+ #
71
+ # Returns a Hash of config options
72
+ def read_conf_file
73
+ conf = {}
74
+
75
+ if File.exists?(@path) && File.size?(@path)
76
+
77
+ # Store valid conf keys
78
+ YAML.load_file(@path).each do |key, value|
79
+
80
+ if @valid_keys.include? key
81
+ conf[key] = value
82
+ else
83
+ say "Invalid configuration key: #{key}"
84
+ end
85
+ end
86
+ end
87
+
88
+ conf
89
+ end
90
+
91
+ # Require the command config option to be set
92
+ # Print error and exit otherwise
93
+ #
94
+ # Returns nothing
95
+ def require_command
96
+ unless @command
97
+ puts "No command present"
98
+ puts "Run rcomp c COMMAND to add a command to test with"
99
+ exit 1
100
+ end
101
+ end
102
+
103
+ # Require the existance of the root directory
104
+ # Print error and exit otherwise
105
+ #
106
+ # Returns nothing
107
+ def require_root_exists
108
+ unless File.exists? @root
109
+ puts "No RComp directory. Run rcomp init to create"
110
+ exit 1
111
+ end
112
+ end
113
+
114
+ # Require all sudirectories of the root directory to exist
115
+ # Print error and exit otherwise
116
+ #
117
+ # Returns nothing
118
+ def require_root_subdirs
119
+ unless File.exists?(@test_root) &&
120
+ File.exists?(@result_root) &&
121
+ File.exists?(@expected_root)
122
+ puts "Missing RComp directories at #{@root}"
123
+ puts "Run rcomp init to repair"
124
+ exit 1
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,22 @@
1
+ module RComp
2
+ module Helper
3
+
4
+ ##
5
+ # Pluralizes a number / phrase pair
6
+ #
7
+ # ex:
8
+ # plural(1, noun) # '1 noun'
9
+ # plural(3, noun) # '3 nouns'
10
+ # plural(3, geese, geese) # '3 geese'
11
+
12
+ def plural(n, singular, plural=nil)
13
+ if n == 1
14
+ "1 #{singular}"
15
+ elsif plural
16
+ "#{n} #{plural}"
17
+ else
18
+ "#{n} #{singular}s"
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/rcomp/path.rb ADDED
@@ -0,0 +1,28 @@
1
+ module RComp
2
+ module Path
3
+ def rel_path(test_path)
4
+ test_path.gsub(Conf.instance.test_root, '')
5
+ end
6
+
7
+ def result_path(test_path, type)
8
+ cmpnts = []
9
+ cmpnts << Conf.instance.result_root
10
+ cmpnts << rel_path(File.dirname(test_path))
11
+ cmpnts << File.basename(test_path, ".*") + (type == :out ? '.out' : '.err')
12
+ File.join(cmpnts)
13
+ end
14
+
15
+ def expected_path(test_path, type)
16
+ puts Conf.instance.expected_root
17
+ puts rel_path(File.dirname(test_path))
18
+ puts File.basename(test_path, ".*") + (type == :out ? '.out' : '.err')
19
+
20
+ cmpnts = []
21
+ cmpnts << Conf.instance.expected_root
22
+ cmpnts << rel_path(File.dirname(test_path))
23
+ cmpnts << File.basename(test_path, ".*") + (type == :out ? '.out' : '.err')
24
+ puts File.join(cmpnts)
25
+ File.join(cmpnts)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ module RComp
2
+ class Reporter
3
+
4
+ include RComp::Helper
5
+
6
+ # Initialize a new Reporter
7
+ #
8
+ # type - The type (Symbol) of the suite
9
+ #
10
+ # Initialize counters for all result types
11
+ def initialize(type)
12
+ @type = type
13
+ @success = 0
14
+ @skipped = 0
15
+ @failed = 0
16
+ end
17
+
18
+ def report(test)
19
+ case test.result
20
+ # success
21
+ when :success
22
+ case @type
23
+ when :test
24
+ puts "\t passed : #{test.relative_path}"
25
+ when :generate
26
+ puts "\tgenerated : #{test.relative_path}"
27
+ end
28
+ @success += 1
29
+
30
+ # skipped
31
+ when :skipped
32
+ case @type
33
+ when :test
34
+ puts "\tskipped : #{test.relative_path}"
35
+ when :generate
36
+ puts "\t skipped : #{test.relative_path}"
37
+ end
38
+ @skipped += 1
39
+
40
+ # failed
41
+ when :failed
42
+ case @type
43
+ when :test
44
+ puts "\t failed : #{test.relative_path}"
45
+ end
46
+ @failed += 1
47
+ end
48
+ end
49
+
50
+ def summary
51
+ case @type
52
+ when :test
53
+ print_test_summary
54
+ when :generate
55
+ print_generate_summary
56
+ end
57
+ exit 1 if @failed > 0
58
+ end
59
+
60
+ private
61
+
62
+ def print_test_summary
63
+ desc = []
64
+ summary = "#{plural((@failed + @skipped + @success), 'test')} ("
65
+ desc << "#{@failed} failed" unless @failed == 0
66
+ desc << "#{@skipped} skipped" unless @skipped == 0
67
+ desc << "#{@success} passed" unless @success == 0
68
+ summary += desc.join(", ") + ")"
69
+ puts summary
70
+ end
71
+
72
+ def print_generate_summary
73
+ desc = []
74
+ summary = "#{plural((@skipped + @success), 'file')} ("
75
+ desc << "#{@skipped} skipped" unless @skipped == 0
76
+ desc << "#{@success} generated" unless @success == 0
77
+ summary += desc.join(", ") + ")"
78
+ puts summary
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,104 @@
1
+ require 'fileutils'
2
+
3
+ module RComp
4
+ module Runner
5
+
6
+ include RComp::Actions
7
+
8
+ # Run a suite of tests
9
+ #
10
+ # suite - An Array of Test objects
11
+ # type - The type (Symbol) of the suite
12
+ # options - A Hash of runner options
13
+ #
14
+ # Returns nothing
15
+ def run_suite(suite, type, options={})
16
+ @conf = Conf.instance
17
+ reporter = Reporter.new(type)
18
+
19
+ suite.each do |test|
20
+ case type
21
+ when :test
22
+ if expected_exists?(test)
23
+ run_test(test)
24
+ end
25
+
26
+ when :generate
27
+ if expected_exists?(test)
28
+ run_generate(test) if options[:overwrite]
29
+ else
30
+ run_generate(test)
31
+ end
32
+ end
33
+
34
+ reporter.report(test)
35
+ end
36
+
37
+ reporter.summary
38
+ end
39
+
40
+
41
+ private
42
+
43
+ # Check the existance of expected out/err for a test
44
+ #
45
+ # test - A Test object
46
+ #
47
+ # Returns a boolean
48
+ def expected_exists?(test)
49
+ File.exists?(test.expected_out_path) ||
50
+ File.exists?(test.expected_err_path)
51
+ end
52
+
53
+ # Run a test storing it's result out and err
54
+ #
55
+ # test - A Test object
56
+ #
57
+ # Returns nothing
58
+ def run_test(test)
59
+ mkpath_to test.result_out_path
60
+ mkpath_to test.result_err_path
61
+ system "#{@conf.command} #{test.test_path} > #{test.result_out_path} 2> #{test.result_err_path}"
62
+ test.result = compare_output(test)
63
+ end
64
+
65
+ # Generate expected output for a test
66
+ #
67
+ # test - A Test object
68
+ #
69
+ # Returns nothing
70
+ def run_generate(test)
71
+ mkpath_to test.expected_out_path
72
+ mkpath_to test.expected_err_path
73
+ system "#{@conf.command} #{test.test_path} > #{test.expected_out_path} 2> #{test.expected_err_path}"
74
+ test.result = :success
75
+ end
76
+
77
+ # Compare the result and expected output of a test that has been run
78
+ #
79
+ # test - A Test object that has been run
80
+ # precondition: expected_exists?(test) is true
81
+ #
82
+ # Returns a Symbol, :success or :failure, conditionally if the test passed
83
+ def compare_output(test)
84
+ exp_out_exists = File.exists?(test.expected_out_path)
85
+ exp_err_exists = File.exists?(test.expected_err_path)
86
+
87
+ if exp_out_exists && exp_err_exists # test out and err
88
+ if FileUtils.identical?(test.expected_out_path, test.result_out_path) &&
89
+ FileUtils.identical?(test.expected_err_path, test.result_err_path)
90
+ return :success
91
+ end
92
+ elsif exp_out_exists # test only out
93
+ if FileUtils.identical?(test.expected_out_path, test.result_out_path)
94
+ return :success
95
+ end
96
+ else # test only err
97
+ if FileUtils.identical?(test.expected_err_path, test.result_err_path)
98
+ return :success
99
+ end
100
+ end
101
+ return :failed
102
+ end
103
+ end
104
+ end