rcomp 0.1.0

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