greybox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6403c4bd8f174968087fb1add29ea027ffb2e502
4
+ data.tar.gz: eaf7e713f00cc65d609a5c80be3f94479da6d42a
5
+ SHA512:
6
+ metadata.gz: c8ca2cc43f5b623785081aff0918db238fd10e052fe2ac23363dd97539d509afec441fc84b7ddf2c55dd04e7aaf596da0ec21c0c007f60944e0d65f2c6f103c0
7
+ data.tar.gz: 39f8f4521871ee01435b2a1a7829f8037352aaf42d56414e5f6755bcf63942f3e985d3620202dab9c3a06224680bcf8a7fdbda4fe78c031216d2db08407bf739
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ script: bundle exec rake test
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Justin Jaffray
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ [![Build Status](https://travis-ci.org/justinj/greybox.png?branch=master)](https://travis-ci.org/justinj/greybox)
2
+ [![Code Climate](https://codeclimate.com/github/justinj/greybox.png)](https://codeclimate.com/github/justinj/greybox)
3
+
4
+ # Greybox
5
+
6
+ Test a CLI program against a black box.
7
+ Intended for school projects.
8
+
9
+ Often when working on a school project, the class will provide a black box to test your programs against.
10
+ Greybox provides a simple testing harness to easily add new test cases whose expected output is automatically generated by this black box.
11
+ Since these might be slow, or even over an ssh connection, it will store the results so it only has to actually run the black box the first time.
12
+
13
+ ## Installation
14
+
15
+ Only Ruby 1.9.3 and 2.0.0 are supported.
16
+
17
+ $ gem install greybox
18
+
19
+ ## Usage
20
+
21
+ In your project directory, create a file called `Greyfile`.
22
+ A `Greyfile` might look like this:
23
+
24
+ ```ruby
25
+ Greybox.setup do |c|
26
+ c.test_command = "./my_test_command < %"
27
+ c.blackbox_command = "./blackbox_command < %"
28
+ c.input = "test/*.input"
29
+ end
30
+ ```
31
+
32
+ This defines a testing setup in which the command
33
+
34
+ $ ./blackbox_command < INPUT_FILE.input
35
+
36
+ is used as the reference for correctness.
37
+ `%` denotes where the input file will be placed.
38
+
39
+ $ ./my_test_command < INPUT_FILE.input
40
+
41
+ is verified to match the blackbox output every time
42
+
43
+ $ greybox
44
+
45
+ is run.
46
+
47
+ The expected results will be stored in `INPUT_FILE.output`.
48
+ This is the default behaviour but can be changed.
49
+
50
+ As a more complete example, here is a Greyfile I used for a school project:
51
+
52
+ ```ruby
53
+ Greybox.setup do |c|
54
+ c.test_command = "racket wlppscan.ss < % 2>&1"
55
+ c.blackbox_command = "ssh school 'java cs241.WLPPScan' < % 2>&1"
56
+ c.input = "test/*.input"
57
+
58
+ # if the expected ERRORs, all we care about is that
59
+ # the actual ERRORs as well.
60
+ c.comparison = ->(actual, expected) do
61
+ if expected =~ /ERROR/
62
+ actual =~ /ERROR/
63
+ else
64
+ expected == actual
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ The `test_command` specifies how to run _my_ version of the program, a racket program.
71
+ `blackbox_command` specifies how to generate the expected output for a specific test case.
72
+
73
+ `comparison` specifies how the output of `test_command` should be compared to the black box output.
74
+ In this case, if part of the output contains `ERROR`, all we care about is that the actual output contains `ERROR` as well.
75
+ Otherwise, they must be equal.
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
82
+ 4. Push to the branch (`git push origin my-new-feature`)
83
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = ["spec/spec_helper"] + FileList['spec/**/*_spec.rb']
7
+ end
8
+
9
+ task :gem do
10
+ sh "yes | gem uninstall greybox; true"
11
+ sh "gem build greybox.gemspec"
12
+ sh "gem install *.gem"
13
+ end
data/bin/greybox ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "greybox"
4
+
5
+ begin
6
+ load Dir.pwd + "/Greyfile"
7
+ rescue LoadError
8
+ puts "Could not load Greyfile"
9
+ end
data/example/Greyfile ADDED
@@ -0,0 +1,5 @@
1
+ Greybox.setup do |c|
2
+ c.input "test/*.input"
3
+ c.blackbox_command "cat < %"
4
+ c.test_command "cat < %"
5
+ end
@@ -0,0 +1 @@
1
+ Hello
@@ -0,0 +1 @@
1
+ Hello
data/greybox.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'greybox/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "greybox"
8
+ spec.version = Greybox::VERSION
9
+ spec.authors = ["Justin Jaffray"]
10
+ spec.email = ["justin.jaffray@gmail.com"]
11
+ spec.description = %q{Test against a black box.}
12
+ spec.summary = %q{Easily test school assignments or other projects where you have a reference}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "diffy"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "fakefs"
26
+ spec.add_development_dependency "mocha"
27
+ end
@@ -0,0 +1,66 @@
1
+ module Greybox
2
+ module Configurable
3
+ attr_accessor :properties
4
+
5
+ module ClassMethods
6
+ attr_accessor :defaults
7
+ attr_accessor :required_properties
8
+ def def_property(name, args = {})
9
+ setter = "#{name}=".to_sym
10
+ getter = name
11
+ (@defaults ||= {})[name] = args[:default] if args.has_key? :default
12
+ (@required_properties ||= Set.new) << name if args[:required]
13
+
14
+ # Stolen from RubyTapas episodes 27 and 28
15
+ mod = if const_defined?(:Properties, false)
16
+ const_get(:Properties)
17
+ else
18
+ const_set(:Properties, new_blank_properties_module)
19
+ end
20
+
21
+ mod.module_eval %Q{
22
+ def #{setter}(value)
23
+ @properties ||= {}
24
+ @properties[:#{name}] = value
25
+ end
26
+
27
+ def #{getter}
28
+ get_prop(:#{name})
29
+ end
30
+ }
31
+
32
+ include mod
33
+ end
34
+
35
+ private
36
+ def new_blank_properties_module
37
+ Module.new do
38
+ class << self
39
+ def to_s
40
+ "Properties(#{properties.join(", ")})"
41
+ end
42
+ alias_method :inspect, :to_s
43
+
44
+ def properties
45
+ instance_methods(false).reject { |m| m.to_s.end_with? "=" }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def get_prop(prop)
53
+ (@properties || {}).fetch(prop, self.class.defaults[prop])
54
+ end
55
+
56
+ def verify
57
+ self.class.required_properties.each do |prop|
58
+ raise "Property #{prop} is required." unless get_prop(prop)
59
+ end
60
+ end
61
+
62
+ def self.included(base)
63
+ base.extend(ClassMethods)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,12 @@
1
+ module Greybox
2
+ class Configuration
3
+ include Configurable
4
+
5
+ def_property :input, required: true
6
+ def_property :test_command, required: true
7
+ def_property :expected, default: ->(input) { input.gsub(/\.input$/, ".output") }
8
+ def_property :comparison, default: ->(actual, expected) { actual == expected }
9
+ def_property :blackbox_command, required: true
10
+
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ module Greybox
2
+ class Runner
3
+ attr_reader :failures
4
+ attr_reader :config
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ @failures = []
9
+ end
10
+ def files
11
+ Dir.glob(config.input)
12
+ end
13
+
14
+ def expected_filename(input_filename)
15
+ expected_filename = config.expected.call(input_filename)
16
+ raise "Output filename for #{input_filename} was same as input!" if expected_filename == input_filename
17
+ expected_filename
18
+ end
19
+
20
+ def expected_for(filename)
21
+ file = expected_filename(filename)
22
+ File.open(file, 'w') do |f|
23
+ f.write run_command(config.blackbox_command, filename)
24
+ end
25
+ File.read(file)
26
+ end
27
+
28
+ def run
29
+ files.each do |input_file|
30
+ expected = expected_for(input_file)
31
+ actual = run_command(config.test_command, input_file)
32
+ report_failure(input_file, expected, actual) unless config.comparison.call(actual, expected)
33
+ end
34
+ end
35
+
36
+ def run_command(command, file)
37
+ `#{insert_filename(command, file)}`
38
+ end
39
+
40
+ def insert_filename(command, filename)
41
+ command.gsub("%", filename)
42
+ end
43
+
44
+ def report_failure(filename, expected, actual)
45
+ @failures << { filename: filename, expected: expected, actual: actual }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Greybox
2
+ VERSION = "0.0.1"
3
+ end
data/lib/greybox.rb ADDED
@@ -0,0 +1,38 @@
1
+ require "set"
2
+ require "diffy"
3
+ require "greybox/version"
4
+ require "greybox/configurable"
5
+ require "greybox/runner"
6
+ require "greybox/configuration"
7
+
8
+ module Greybox
9
+ class << self
10
+ attr_reader :failures
11
+ attr_reader :configuration
12
+ attr_reader :runner
13
+ def setup(&blk)
14
+ config(&blk)
15
+ run
16
+ check
17
+ end
18
+
19
+ def run
20
+ @runner = Runner.new(configuration)
21
+ runner.run
22
+ end
23
+
24
+ def check
25
+ runner.failures.each do |failure|
26
+ puts "FAILED:"
27
+ puts "filename: #{failure[:filename]}"
28
+ puts Diffy::Diff.new(failure[:expected], failure[:actual])
29
+ end
30
+ end
31
+
32
+ def config
33
+ @configuration = Configuration.new
34
+ yield configuration
35
+ configuration.verify
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module Greybox
2
+ describe Greybox do
3
+ describe "errors" do
4
+ it "complains if a missing property is gone before being run" do
5
+ -> do
6
+ Greybox.config do |c|
7
+ end
8
+ end.must_raise RuntimeError
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,108 @@
1
+ module Greybox
2
+ describe Runner do
3
+ before do
4
+ FakeFS.activate!
5
+
6
+ FileUtils.touch "inputfile.input"
7
+ end
8
+
9
+ after do
10
+ FakeFS.activate!
11
+ Dir.glob("*").each { |f| FileUtils.rm f }
12
+ FakeFS.deactivate!
13
+ end
14
+
15
+ # it should be noted these are just the defaults
16
+ # for these tests to reduce duplication,
17
+ # the real-world defaults are different
18
+ def defaults
19
+ {
20
+ input: "*.input",
21
+ expected: ->(input) { "output_file" },
22
+ blackbox_command: "echo BLACKBOX_COMMAND",
23
+ comparison: ->(actual, expected) { actual == expected },
24
+ }
25
+ end
26
+
27
+ attr_reader :runner
28
+
29
+ def with_config(config = {})
30
+ @runner = Runner.new(stub(defaults.merge(config)))
31
+ end
32
+
33
+ describe "finding files" do
34
+ it "uses the glob to find files" do
35
+ with_config
36
+ runner.files.must_equal ["inputfile.input"]
37
+ end
38
+
39
+ it "uses the 'expected' value to determine the expected name" do
40
+ with_config(expected: ->(input) {
41
+ input.gsub(/\.input$/, ".output")
42
+ })
43
+ runner.expected_filename("hello.input").must_equal "hello.output"
44
+ end
45
+
46
+ it "complains if the output is the same as the input" do
47
+ with_config(expected: ->(input){ input })
48
+ ->() { runner.expected_filename("input.input") }.must_raise RuntimeError
49
+ end
50
+ end
51
+
52
+ describe "running commands" do
53
+ it "uses the provided test command" do
54
+ with_config(test_command: "echo TEST_COMMAND")
55
+ runner.expects(:`).with("echo TEST_COMMAND")
56
+ runner.expects(:`).with("echo BLACKBOX_COMMAND")
57
+ runner.run
58
+ end
59
+
60
+ it "inserts filenames for %'s" do
61
+ with_config(test_command: "echo hi %")
62
+ runner.expects(:`).with "echo hi inputfile.input"
63
+ runner.expects(:`).with "echo BLACKBOX_COMMAND"
64
+ runner.run
65
+ end
66
+
67
+ it "uses the provided blackbox command to find the expected value for cases that don't have one yet" do
68
+ with_config(test_command: "echo hi",
69
+ blackbox_command: "echo output")
70
+ runner.expects(:`).with "echo hi"
71
+ runner.expects(:`).with "echo output"
72
+ runner.run
73
+ end
74
+
75
+ it "inserts output filenames for %'s in the blackbox command" do
76
+ with_config(test_command: "echo hi",
77
+ blackbox_command: "echo output < %")
78
+ runner.expects(:`).with "echo hi"
79
+ runner.expects(:`).with "echo output < inputfile.input"
80
+ runner.run
81
+ end
82
+ end
83
+
84
+ describe "asserting output" do
85
+ it "compares for equality by default" do
86
+ with_config(test_command: "echo hi",
87
+ blackbox_command: "echo hi")
88
+ runner.run
89
+ runner.failures.must_be_empty
90
+ end
91
+
92
+ it "has a failure if a test fails" do
93
+ with_config(test_command: "echo hi",
94
+ blackbox_command: "echo hello")
95
+ runner.run
96
+ runner.failures.wont_be_empty
97
+ end
98
+
99
+ it "compares by the provided comparison" do
100
+ with_config(comparison: ->(actual, expected) { actual == "hi\n" },
101
+ test_command: "echo hi",
102
+ blackbox_command: "echo hello")
103
+ runner.run
104
+ runner.failures.must_be_empty
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,5 @@
1
+ require "minitest/autorun"
2
+ require "mocha/setup"
3
+ require "fakefs"
4
+ require "fileutils"
5
+ require_relative "../lib/greybox"
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: greybox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Jaffray
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: diffy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakefs
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Test against a black box.
84
+ email:
85
+ - justin.jaffray@gmail.com
86
+ executables:
87
+ - greybox
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .travis.yml
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/greybox
98
+ - example/Greyfile
99
+ - example/test/file1.input
100
+ - example/test/file1.output
101
+ - greybox.gemspec
102
+ - lib/greybox.rb
103
+ - lib/greybox/configurable.rb
104
+ - lib/greybox/configuration.rb
105
+ - lib/greybox/runner.rb
106
+ - lib/greybox/version.rb
107
+ - spec/greybox_spec.rb
108
+ - spec/runner_spec.rb
109
+ - spec/spec_helper.rb
110
+ homepage: ''
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.0.3
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Easily test school assignments or other projects where you have a reference
134
+ test_files:
135
+ - spec/greybox_spec.rb
136
+ - spec/runner_spec.rb
137
+ - spec/spec_helper.rb