qunited 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Aaron Royer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # QUnited
2
+
3
+ QUnited is a tool to run headless JavaScript tests with QUnit.
4
+
5
+ Right now it exists in a proof-of-concept phase and only runs tests with Rhino/Envjs. Give it a try and let me know if you have any feedback.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ $ gem install qunited
11
+ ```
12
+
13
+ ## Running Tests
14
+
15
+ Add the QUnited Rake task to your Rakefile.
16
+
17
+ ```ruby
18
+ require 'qunited/rake_task'
19
+
20
+ QUnited::RakeTask.new do |t|
21
+ t.source_files_pattern = 'lib/js/**/*.js'
22
+ t.test_files_pattern = 'test/js/**/*.js'
23
+ end
24
+ ```
25
+
26
+ Source and test files can also be configured as an array of file names. This may be desirable for source files since the order of their execution is often important. A glob pattern may not order the files correctly but configuring the task with an array can guarantee they are executed in the correct order.
27
+
28
+ Note that all JavaScript dependencies will have to be loaded with source files. They will often need to be loaded before your own code so using an array to configure source files may be appropriate.
29
+
30
+ ```ruby
31
+ require 'qunited/rake_task'
32
+
33
+ QUnited::RakeTask.new do |t|
34
+ t.source_files = ['lib/js/jquery.js', 'lib/js/my_utils.js', 'lib/js/my_app.js']
35
+ t.test_files = ['test/js/test_my_utils.js', 'test/js/test_my_app.js']
36
+ end
37
+ ```
38
+
39
+ Note that you can also use an array to configure the test files but a glob pattern is usually more convenient since test files usually do not need to be loaded in a particular order.
40
+
41
+ ## Dependencies
42
+
43
+ Right now only Rhino is set up to run tests. This means you'll need to have Java (version 1.1 minimum) in your path to use QUnited.
44
+
45
+ ## Attribution
46
+
47
+ QUnited builds on work done on the following projects:
48
+
49
+ [QUnit](https://github.com/jquery/qunit/) QUnit is a nice little JavaScript testing library and is, of course, central to what this project does.
50
+
51
+ [Rhino](http://www.mozilla.org/rhino/) Rhino is a JavaScript interpreter that runs on the JVM.
52
+
53
+ [Envjs](http://www.envjs.com/) Envjs is a simulated browser environment written in JavaScript.
54
+
55
+ ## License
56
+
57
+ QUnited is MIT licensed
@@ -0,0 +1,56 @@
1
+ module QUnited
2
+ module Driver
3
+ class Base
4
+ attr_reader :results, :source_files, :test_files
5
+
6
+ # Finds an executable on the PATH. Returns the absolute path of the
7
+ # executable if found, otherwise nil.
8
+ def self.which(cmd)
9
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
10
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
11
+ exts.each do |ext|
12
+ exe = "#{path}/#{cmd}#{ext}"
13
+ return exe if File.executable? exe
14
+ end
15
+ end
16
+ return nil
17
+ end
18
+
19
+ # Get the path of the common (to all drivers) supporting files directory
20
+ def self.support_dir
21
+ @@support_dir = File.expand_path('../support', __FILE__)
22
+ end
23
+
24
+ # Array of file names? Glob pattern?
25
+ def initialize(source_files, test_files)
26
+ @source_files = if source_files.is_a? String
27
+ Dir.glob(source_files)
28
+ elsif source_files.is_a? Array
29
+ source_files
30
+ end
31
+
32
+ @test_files = if test_files.is_a? String
33
+ Dir.glob(test_files)
34
+ elsif test_files.is_a? Array
35
+ test_files
36
+ end
37
+ end
38
+
39
+ def run
40
+ raise 'run not implemented'
41
+ end
42
+
43
+ def support_file_path(filename)
44
+ File.join(self.class.support_dir, filename)
45
+ end
46
+
47
+ def support_file_contents(filename)
48
+ IO.read(support_file_path(filename))
49
+ end
50
+
51
+ def name
52
+ self.class.name.split('::')[-1]
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,69 @@
1
+ require 'pathname'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+ require 'erb'
5
+ require 'open3'
6
+
7
+ module QUnited
8
+ module Driver
9
+ class PhantomJs < Base
10
+
11
+ # Determines whether this driver available to use.
12
+ # Checks whether phantomjs is on the PATH.
13
+ def self.available?
14
+ !!which('phantomjs')
15
+ end
16
+
17
+ def name
18
+ "PhantomJS" # Slightly more accurate than our class name
19
+ end
20
+
21
+ def run
22
+ self.tests_file = Tempfile.new(['tests_page', '.html'])
23
+ tests_file.write(tests_page_content)
24
+ tests_file.close
25
+
26
+ results_file = Tempfile.new('qunited_results')
27
+ results_file.close
28
+
29
+ cmd = %{phantomjs "#{File.expand_path('../support/runner.js', __FILE__)}" }
30
+ cmd << %{#{tests_file.path} #{results_file.path}}
31
+
32
+ Open3.popen3(cmd) do |stdin, stdout, stderr|
33
+ # PhantomJS sometimes puts error messages to stdout - redirect them to stderr
34
+ [stdout, stderr].each do |io|
35
+ unless (io_str = io.read).strip.empty? then $stderr.puts(io_str) end
36
+ end
37
+ end
38
+
39
+ @results = ::QUnited::Results.from_javascript_produced_yaml(IO.read(results_file))
40
+ end
41
+
42
+ private
43
+
44
+ attr_accessor :tests_file
45
+
46
+ def tests_page_content
47
+ ERB.new(IO.read(File.expand_path('../support/tests_page.html.erb', __FILE__))).result(binding)
48
+ end
49
+
50
+ def script_tag(file)
51
+ js_file_path, tests_file_path = Pathname.new(file).realpath, Pathname.new(tests_file)
52
+ begin
53
+ rel_path = js_file_path.relative_path_from(tests_file_path)
54
+ # Attempt to convert paths to relative URLs if Windows... should really test this
55
+ return %{<script type="text/javascript" src="#{rel_path.to_s.gsub(/\\/, '/')}"></script>}
56
+ rescue ArgumentError
57
+ # If we cannot get a relative path to the js file then just put the contents
58
+ # of the file inline. This can happen for a few reasons, like if the drive
59
+ # letter is different on Windows.
60
+ return <<-SCRIPT_ELEMENT
61
+ <script type="text/javascript">
62
+ #{IO.read(file)}
63
+ </script>
64
+ SCRIPT_ELEMENT
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,103 @@
1
+ /*
2
+ Portions of this file are from the PhantomJS project from Ofi Labs.
3
+
4
+ Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
5
+ Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ * Neither the name of the <organization> nor the
16
+ names of its contributors may be used to endorse or promote products
17
+ derived from this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
23
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ var system = require('system'), fs = require("fs");
32
+
33
+ /**
34
+ * Wait until the test condition is true or a timeout occurs. Useful for waiting
35
+ * on a server response or for a ui change (fadeIn, etc.) to occur.
36
+ *
37
+ * @param testFx javascript condition that evaluates to a boolean,
38
+ * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
39
+ * as a callback function.
40
+ * @param onReady what to do when testFx condition is fulfilled,
41
+ * it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
42
+ * as a callback function.
43
+ * @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
44
+ */
45
+ function waitFor(testFx, onReady, timeOutMillis) {
46
+ var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timeout is 3s
47
+ start = new Date().getTime(),
48
+ condition = false,
49
+ interval = setInterval(function() {
50
+ if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
51
+ // If not time-out yet and condition not yet fulfilled
52
+ condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
53
+ } else {
54
+ if (!condition) {
55
+ // If condition still not fulfilled (timeout but condition is 'false')
56
+ console.log("ERROR: Timeout waiting for tests to complete");
57
+ phantom.exit(1);
58
+ } else {
59
+ // Condition fulfilled (timeout and/or condition is 'true')
60
+ //console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
61
+ typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
62
+ clearInterval(interval); //< Stop this interval
63
+ }
64
+ }
65
+ }, 100);
66
+ };
67
+
68
+
69
+ if (system.args.length < 2) {
70
+ console.log('No tests file specified');
71
+ phantom.exit(1);
72
+ } else if (system.args.length < 3) {
73
+ console.log('No results output file specified');
74
+ phantom.exit(1);
75
+ }
76
+
77
+ var page = require('webpage').create(),
78
+ tests_html_file = system.args[1],
79
+ results_output_file = system.args[2];
80
+
81
+ page.open(tests_html_file, function(status) {
82
+ if (status !== "success") {
83
+ console.log("Could not open tests file");
84
+ phantom.exit(1);
85
+ } else {
86
+ waitFor(function(){
87
+ // Done when all tests have run (the results have been rendered)
88
+ return page.evaluate(function(){
89
+ var el = document.getElementById('qunit-testresult');
90
+ if (el && el.innerText.match('completed')) {
91
+ return true;
92
+ }
93
+ return false;
94
+ });
95
+ }, function(){
96
+ // Results should have been collected with code in qunited.js. Check that file
97
+ // for more details. Grab the YAML it outputs and write it to the results file.
98
+ var results = page.evaluate(function() { return QUnited.collectedTestResultsAsYaml(); });
99
+ fs.write(results_output_file, results, 'a');
100
+ phantom.exit(0);
101
+ });
102
+ }
103
+ });
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>QUnit Test Suite</title>
5
+ </head>
6
+ <body>
7
+ <h1 id="qunit-header">QUnit Test Suite</h1>
8
+ <h2 id="qunit-banner"></h2>
9
+ <div id="qunit-testrunner-toolbar"></div>
10
+ <h2 id="qunit-userAgent"></h2>
11
+ <ol id="qunit-tests"></ol>
12
+ </body>
13
+ <%= script_tag support_file_path('qunit.js') %>
14
+ <%= script_tag support_file_path('yaml.js') %>
15
+ <%= script_tag support_file_path('qunited.js') %>
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ QUnited.startCollectingTestResults();
19
+ </script>
20
+
21
+ <% source_files.each do |source_file| %>
22
+ <%= script_tag source_file %>
23
+ <% end %>
24
+ <% test_files.each do |test_file| %>
25
+ <%= script_tag test_file %>
26
+ <% end %>
27
+
28
+ </html>
@@ -0,0 +1,51 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+ require 'open3'
4
+
5
+ module QUnited
6
+ module Driver
7
+ class Rhino < Base
8
+
9
+ # Determines whether this driver available to use. Checks whether java
10
+ # is on the PATH and whether Java is version 1.1 or greater.
11
+ def self.available?
12
+ java_exe = which('java')
13
+ if java_exe
14
+ stdin, stdout, stderr = Open3.popen3('java -version')
15
+ begin
16
+ version = Float(stderr.read.split("\n").first[/(\d+\.\d+)/, 1])
17
+ version >= 1.1
18
+ rescue
19
+ false
20
+ end
21
+ end
22
+ end
23
+
24
+ def run
25
+ support_dir = File.expand_path('../support', __FILE__)
26
+ js_jar, runner = File.join(support_dir, 'js.jar'), File.join(support_dir, 'runner.js')
27
+
28
+ source_files_args = @source_files.map { |sf| %{"#{sf}"} }.join(' ')
29
+ test_files_args = @test_files.map { |tf| %{"#{tf}"} }.join(' ')
30
+
31
+ results_file = Tempfile.new('qunited_results')
32
+ results_file.close
33
+
34
+ cmd = %{java -jar "#{js_jar}" -opt -1 "#{runner}" }
35
+ cmd << %{"#{QUnited::Driver::Base.support_dir}" "#{support_dir}" "#{results_file.path}"}
36
+ cmd << " #{source_files_args} -- #{test_files_args}"
37
+
38
+ # Swallow stdout but allow stderr to get blasted out to console - if there are uncaught
39
+ # exceptions or anything else that goes wrong with the JavaScript interpreter the user
40
+ # will probably want to know but we are not particularly interested in it.
41
+ Open3.popen3(cmd) do |stdin, stdout, stderr|
42
+ stdout.each {||} # Ignore; this is just here to make sure we block
43
+ # while waiting for tests to finish
44
+ unless (err = stderr.read).strip.empty? then $stderr.puts(err) end
45
+ end
46
+
47
+ @results = ::QUnited::Results.from_javascript_produced_yaml(IO.read(results_file))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,23 +1,27 @@
1
1
  // Runs QUnit tests with Envjs and outputs test results as
2
2
  // an array of data serialized in YAML format.
3
3
  //
4
- // The first argument should be the lib directory containing JavaScript dependencies. The second
5
- // argument is the file to use for test results output. The next arguments are source JavaScript
6
- // files to test, until "--" is encountered. After the "--" the rest of the arguments are QUnit
4
+ // The first argument should be the lib directory containing common QUnited dependencies. The second
5
+ // argument is the directory containing Rhino driver specific dependencies. The third argument
6
+ // is the file to use for test results output. The next arguments are source JavaScript files to
7
+ // test, until "--" is encountered. After the "--" the rest of the arguments are QUnit
7
8
  // test files.
8
9
  //
9
10
  // Example:
10
- // java -jar js.jar -opt -1 qunit-runner.js libdir outfile.yaml source.js -- test1.js test2.js
11
+ // java -jar js.jar -opt -1 runner.js commonlibdir libdir outfile.yaml source.js -- test1.js test2.js
11
12
  // ^ our args start here
12
13
 
13
14
  var QUnited = { sourceFiles: [], testFiles: [] };
14
15
 
15
16
  (function(args) {
16
- var libDir = args.shift();
17
+ var commonLibDir = args.shift(),
18
+ libDir = args.shift();
17
19
  QUnited.outputFile = args.shift();
18
20
 
19
- ['env.rhino.js', 'qunit.js', 'yaml.js'].forEach(function(lib) {
20
- load(libDir + '/' + lib);
21
+ load(libDir + '/env.rhino.js');
22
+
23
+ ['qunit.js', 'yaml.js'].forEach(function(lib) {
24
+ load(commonLibDir + '/' + lib);
21
25
  });
22
26
 
23
27
  var readingSource = true;
@@ -94,7 +98,7 @@ QUnited.testFiles.forEach(function(file) {
94
98
  // stderr and keeps on going without letting the caller of load() know what happened!
95
99
  //
96
100
  // Another option is to slurp in the file, eval it, and try/catch the errors and handle them
97
- // accordingly. But, I found that the slurp and eval approach introduced to many subtle
101
+ // accordingly. But, I found that the slurp and eval approach introduced too many subtle
98
102
  // misbehaviors to be worth it.
99
103
  //
100
104
  // The thing is, if a test file crashes and the tests aren't run then the build will succeed and
@@ -0,0 +1,76 @@
1
+ var QUnited = QUnited || {};
2
+
3
+ (function() {
4
+
5
+ QUnited.util = {};
6
+ QUnited.util.dateToString = function(date) {
7
+ if (Object.prototype.toString.call(date) === '[object String]') { return date; }
8
+ function pad(n) { return n < 10 ? '0' + n : n; }
9
+ return date.getUTCFullYear() + '-' + pad(date.getUTCMonth() + 1)+'-' + pad(date.getUTCDate()) + 'T' +
10
+ pad(date.getUTCHours()) + ':' + pad(date.getUTCMinutes()) + ':' + pad(date.getUTCSeconds()) + 'Z';
11
+ };
12
+
13
+ QUnited.startCollectingTestResults = function() {
14
+ // Various state we'll need while running the tests
15
+ QUnited.modulesMap = {};
16
+ QUnited.currentTestFile = null; // Set when loading files, see below
17
+ var currentModule, currentTest;
18
+
19
+ ///// Listen for QUnit events during tests
20
+
21
+ QUnit.testStart(function(data) {
22
+ currentTest = {
23
+ name: data.name,
24
+ assertion_data: [], // Ruby-style, since we'll be reading it with Ruby
25
+ start: new Date(),
26
+ assertions: 0,
27
+ file: QUnited.currentTestFile
28
+ };
29
+
30
+ var moduleName = data.module || "(no module)",
31
+ module = QUnited.modulesMap[moduleName];
32
+ if (!module) {
33
+ module = {name: moduleName, tests: []};
34
+ QUnited.modulesMap[moduleName] = module;
35
+ }
36
+ module.tests.push(currentTest);
37
+ });
38
+
39
+ QUnit.testDone(function(data) {
40
+ currentTest.duration = ((new Date()).getTime() - currentTest.start.getTime()) / 1000;
41
+ currentTest.failed = data.failed;
42
+ currentTest.total = data.total;
43
+ });
44
+
45
+ /*
46
+ * Called on every assertion AND whenever we have an expect(num) fail. You cannot tell this
47
+ * apart from an assertion (even though you could make a good guess) with certainty so just
48
+ * don't worry about it as it will only throw assertions count off on a failing test.
49
+ */
50
+ QUnit.log(function(data) {
51
+ currentTest.assertions++;
52
+ currentTest.assertion_data.push(data);
53
+ });
54
+ };
55
+
56
+ QUnited.collectedTestResultsAsYaml = function() {
57
+ var modules = [];
58
+
59
+ // Make a modules array for outputing results
60
+ Object.keys(QUnited.modulesMap).forEach(function(key) {
61
+ var mod = QUnited.modulesMap[key],
62
+ tests = mod.tests;
63
+ modules.push(mod);
64
+ tests.forEach(function(test) {
65
+ // YAML serializer doesn't seem to do dates; make them strings
66
+ test.start = QUnited.util.dateToString(test.start);
67
+ // Convert the duration to a string since the YAML serializer makes them all 0s otherwise
68
+ test.duration = "" + test.duration;
69
+ });
70
+ });
71
+
72
+ // Write all the results as YAML
73
+ return YAML.encode(modules);
74
+ };
75
+
76
+ })();
@@ -0,0 +1,3 @@
1
+ require 'qunited/driver/base'
2
+ require 'qunited/driver/rhino/rhino'
3
+ require 'qunited/driver/phantomjs/phantomjs'
@@ -1,3 +1,5 @@
1
+ require 'yaml'
2
+
1
3
  module QUnited
2
4
 
3
5
  # Simple tests results compiler. Takes a raw results hash that was produced by a runner.
@@ -79,6 +81,10 @@ module QUnited
79
81
  end
80
82
  end
81
83
 
84
+ def self.from_javascript_produced_yaml(yaml)
85
+ self.new clean_up_results(YAML.load(yaml))
86
+ end
87
+
82
88
  def initialize(modules_results_array)
83
89
  @data = modules_results_array.freeze
84
90
  @module_results = @data.map { |module_data| ModuleResults.new module_data }
@@ -160,5 +166,28 @@ module QUnited
160
166
  def errors
161
167
  @errors ||= assertions.select { |assert| assert.error? }
162
168
  end
169
+
170
+ # The YAML serializing JavaScript library does not put things into the cleanest form
171
+ # for us to work with. This turns the String keys into Symbols and converts Strings
172
+ # representing dates and numbers into their appropriate objects.
173
+ def self.clean_up_results(results)
174
+ results.map! { |mod_results| symbolize_keys mod_results }
175
+ results.each do |mod_results|
176
+ mod_results[:tests].map! { |test| clean_up_test_results(symbolize_keys(test)) }
177
+ end
178
+ end
179
+
180
+ def self.clean_up_test_results(test_results)
181
+ test_results[:start] = DateTime.parse(test_results[:start])
182
+ test_results[:duration] = Float(test_results[:duration])
183
+ test_results[:assertion_data].map! { |data| symbolize_keys data }
184
+ test_results
185
+ end
186
+
187
+ def self.symbolize_keys(hash)
188
+ new_hash = {}
189
+ hash.keys.each { |key| new_hash[key.to_sym] = hash[key] }
190
+ new_hash
191
+ end
163
192
  end
164
193
  end
@@ -1,23 +1,23 @@
1
1
  module QUnited
2
2
  class Runner
3
+
4
+ # The drivers in order of which to use first when not otherwise specified
5
+ DRIVERS = [:PhantomJs, :Rhino].map { |driver| ::QUnited::Driver.const_get(driver) }.freeze
6
+
3
7
  def self.run(js_source_files, js_test_files)
4
- js_runner_klass = self.js_runner
5
- # TODO: test that this JsRunner can run with current environment
6
- runner = js_runner_klass.new(js_source_files, js_test_files)
8
+ driver_class = self.best_available_driver
9
+ runner = driver_class.new(js_source_files, js_test_files)
7
10
 
8
11
  puts "\n# Running JavaScript tests with #{runner.name}:\n\n"
9
12
 
10
- results = runner.run.results
13
+ results = runner.run
11
14
  puts results
12
15
  results.to_i
13
16
  end
14
17
 
15
18
  # Get the runner that we will be using to run the JavaScript tests.
16
- #
17
- # Right now we only have one JavaScript runner, but when we have multiple we will have to
18
- # determine which one we will used unless explicitly configured.
19
- def self.js_runner
20
- ::QUnited::JsRunner::Rhino
19
+ def self.best_available_driver
20
+ DRIVERS.find { |driver| driver.available? }
21
21
  end
22
22
  end
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module QUnited
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/qunited.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'qunited/version'
2
- require 'qunited/runner'
3
2
  require 'qunited/results'
4
- require 'qunited/js_runner'
3
+ require 'qunited/driver'
4
+ require 'qunited/runner'
@@ -0,0 +1,70 @@
1
+ # Common driver tests that should pass for any implementation of
2
+ # QUnited::Driver::Base. There are also a few utility methods included.
3
+ module QUnited::DriverCommonTests
4
+ def test_driver_available
5
+ assert driver_class.available?, 'Driver should be available - if it is not then ' +
6
+ 'either the available? method has a bug or you do not have the proper environment ' +
7
+ "to run the driver; check the available? method in the #{driver_class} driver class " +
8
+ 'to get an idea of whether you should be able to run the driver'
9
+ end
10
+
11
+ def test_running_basic_tests
12
+ results = run_for_project('basic_project')
13
+ assert_equal 3, results.total_tests, 'Correct number of tests run'
14
+ assert_equal 4, results.total_assertions, 'Correct number of assertions executed'
15
+ assert_equal 0, results.total_failures, 'Correct number of failures given'
16
+ end
17
+
18
+ # Make sure we can run tests with DOM operations
19
+ def test_running_dom_tests
20
+ results = run_for_project('dom_project')
21
+ assert_equal 1, results.total_tests, 'Correct number of tests run'
22
+ assert_equal 2, results.total_assertions, 'Correct number of assertions executed'
23
+ assert_equal 0, results.total_failures, 'Correct number of failures given'
24
+ end
25
+
26
+ def test_failures_are_recorded_correctly
27
+ results = run_for_project('failures_project')
28
+ assert_equal 4, results.total_tests, 'Correct number of tests run'
29
+ # QUnit calls the log callback (the same it calls for assertions) every time there
30
+ # is a failed expect(num). So add one to this total.
31
+ assert_equal 5 + 1, results.total_assertions, 'Correct number of assertions executed'
32
+ assert_equal 4, results.total_failures, 'Correct number of failures given'
33
+ end
34
+
35
+ def test_syntax_error_in_test
36
+ runner = driver_class.new(
37
+ [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/no_error.js')],
38
+ [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_syntax_error.js'),
39
+ File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_no_errors_in_it.js')])
40
+
41
+ stderr = capture_stderr { runner.run }
42
+ assert stderr.size > 10, 'Got some stderr output to describe the crash'
43
+ results = runner.results
44
+ assert runner.results.failed?, 'Should fail if syntax error in test'
45
+ end
46
+
47
+ protected
48
+
49
+ def driver_class
50
+ raise 'Must implement driver_class and return the driver class being tested'
51
+ end
52
+
53
+ def run_for_project(project_name)
54
+ runner = runner_for_project(project_name)
55
+ runner.run
56
+ end
57
+
58
+ def runner_for_project(project_name)
59
+ Dir.chdir File.join(FIXTURES_DIR, project_name)
60
+ driver_class.new("app/assets/javascripts/*.js", "test/javascripts/*.js")
61
+ end
62
+
63
+ def capture_stderr
64
+ previous_stderr, $stderr = $stderr, StringIO.new
65
+ yield
66
+ $stderr.string
67
+ ensure
68
+ $stderr = previous_stderr
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+ require File.expand_path('../driver_common_tests', __FILE__)
3
+ require 'stringio'
4
+
5
+ # Test running tests with the PhantomJs driver.
6
+ class TestPhantomJsDriver < MiniTest::Unit::TestCase
7
+ include QUnited::DriverCommonTests
8
+
9
+ private
10
+
11
+ def driver_class
12
+ QUnited::Driver::PhantomJs
13
+ end
14
+
15
+ end
@@ -1,36 +1,13 @@
1
1
  require File.expand_path('../../test_helper', __FILE__)
2
+ require File.expand_path('../driver_common_tests', __FILE__)
2
3
  require 'stringio'
3
4
 
4
- # Test running tests with the Rhino test runner. These
5
- # are really more integration tests than unit tests.
6
- class TestRhinoRunner < MiniTest::Unit::TestCase
7
-
8
- def test_running_basic_tests
9
- results = runner_for_project('basic_project').run.results
10
- assert_equal 3, results.total_tests, 'Correct number of tests run'
11
- assert_equal 4, results.total_assertions, 'Correct number of assertions executed'
12
- assert_equal 0, results.total_failures, 'Correct number of failures given'
13
- end
14
-
15
- # Make sure we can run tests with DOM operations
16
- def test_running_dom_tests
17
- results = runner_for_project('dom_project').run.results
18
- assert_equal 1, results.total_tests, 'Correct number of tests run'
19
- assert_equal 2, results.total_assertions, 'Correct number of assertions executed'
20
- assert_equal 0, results.total_failures, 'Correct number of failures given'
21
- end
22
-
23
- def test_failures_are_recorded_correctly
24
- results = runner_for_project('failures_project').run.results
25
- assert_equal 4, results.total_tests, 'Correct number of tests run'
26
- # QUnit calls the log callback (the same it calls for assertions) every time there
27
- # is a failed expect(num). So add one to this total.
28
- assert_equal 5 + 1, results.total_assertions, 'Correct number of assertions executed'
29
- assert_equal 4, results.total_failures, 'Correct number of failures given'
30
- end
5
+ # Test running tests with the Rhino driver.
6
+ class TestRhinoDriver < MiniTest::Unit::TestCase
7
+ include QUnited::DriverCommonTests
31
8
 
32
9
  def test_undefined_error_in_source
33
- runner = QUnited::JsRunner::Rhino.new(
10
+ runner = QUnited::Driver::Rhino.new(
34
11
  [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/undefined_error.js')],
35
12
  [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_no_errors_in_it.js')])
36
13
 
@@ -44,7 +21,7 @@ class TestRhinoRunner < MiniTest::Unit::TestCase
44
21
  end
45
22
 
46
23
  def test_syntax_error_in_source
47
- runner = QUnited::JsRunner::Rhino.new(
24
+ runner = QUnited::Driver::Rhino.new(
48
25
  [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/syntax_error.js')],
49
26
  [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_no_errors_in_it.js')])
50
27
 
@@ -58,7 +35,7 @@ class TestRhinoRunner < MiniTest::Unit::TestCase
58
35
  end
59
36
 
60
37
  def test_undefined_error_in_test
61
- runner = QUnited::JsRunner::Rhino.new(
38
+ runner = QUnited::Driver::Rhino.new(
62
39
  [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/no_error.js')],
63
40
  [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_undefined_error.js')])
64
41
 
@@ -74,20 +51,8 @@ class TestRhinoRunner < MiniTest::Unit::TestCase
74
51
  assert_equal 1, results.total_errors, 'Correct number of errors given'
75
52
  end
76
53
 
77
- def test_syntax_error_in_test
78
- runner = QUnited::JsRunner::Rhino.new(
79
- [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/no_error.js')],
80
- [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_syntax_error.js'),
81
- File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_no_errors_in_it.js')])
82
-
83
- stderr = capture_stderr { runner.run }
84
- assert stderr.size > 10, 'Got some stderr output to describe the crash'
85
- results = runner.results
86
- assert runner.results.failed?, 'Should fail if syntax error in test'
87
- end
88
-
89
54
  def test_no_tests_in_test_file_means_failure
90
- runner = QUnited::JsRunner::Rhino.new(
55
+ runner = QUnited::Driver::Rhino.new(
91
56
  [File.join(FIXTURES_DIR, 'errors_project/app/assets/javascripts/no_error.js')],
92
57
  [File.join(FIXTURES_DIR, 'errors_project/test/javascripts/this_test_has_no_tests.js')])
93
58
  runner.run
@@ -98,17 +63,8 @@ class TestRhinoRunner < MiniTest::Unit::TestCase
98
63
 
99
64
  private
100
65
 
101
- def runner_for_project(project_name)
102
- Dir.chdir File.join(FIXTURES_DIR, project_name)
103
- QUnited::JsRunner::Rhino.new("app/assets/javascripts/*.js", "test/javascripts/*.js")
104
- end
105
-
106
- def capture_stderr
107
- previous_stderr, $stderr = $stderr, StringIO.new
108
- yield
109
- $stderr.string
110
- ensure
111
- $stderr = previous_stderr
66
+ def driver_class
67
+ QUnited::Driver::Rhino
112
68
  end
113
69
 
114
70
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qunited
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-30 00:00:00.000000000 Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: QUnited runs headless QUnit tests as part of your normal build
15
15
  email:
@@ -21,17 +21,23 @@ extra_rdoc_files: []
21
21
  files:
22
22
  - .gitignore
23
23
  - Gemfile
24
+ - MIT-LICENSE
25
+ - README.md
24
26
  - Rakefile
25
27
  - bin/qunited
26
28
  - lib/qunited.rb
27
- - lib/qunited/js_runner.rb
28
- - lib/qunited/js_runner/base.rb
29
- - lib/qunited/js_runner/rhino.rb
30
- - lib/qunited/js_runner/rhino/js/env.rhino.js
31
- - lib/qunited/js_runner/rhino/js/js.jar
32
- - lib/qunited/js_runner/rhino/js/qunit-runner.js
33
- - lib/qunited/js_runner/rhino/js/qunit.js
34
- - lib/qunited/js_runner/rhino/js/yaml.js
29
+ - lib/qunited/driver.rb
30
+ - lib/qunited/driver/base.rb
31
+ - lib/qunited/driver/phantomjs/phantomjs.rb
32
+ - lib/qunited/driver/phantomjs/support/runner.js
33
+ - lib/qunited/driver/phantomjs/support/tests_page.html.erb
34
+ - lib/qunited/driver/rhino/rhino.rb
35
+ - lib/qunited/driver/rhino/support/env.rhino.js
36
+ - lib/qunited/driver/rhino/support/js.jar
37
+ - lib/qunited/driver/rhino/support/runner.js
38
+ - lib/qunited/driver/support/qunit.js
39
+ - lib/qunited/driver/support/qunited.js
40
+ - lib/qunited/driver/support/yaml.js
35
41
  - lib/qunited/rake_task.rb
36
42
  - lib/qunited/results.rb
37
43
  - lib/qunited/runner.rb
@@ -53,8 +59,10 @@ files:
53
59
  - test/fixtures/failures_project/test/javascripts/test_basics.js
54
60
  - test/fixtures/failures_project/test/javascripts/test_math.js
55
61
  - test/test_helper.rb
62
+ - test/unit/driver_common_tests.rb
63
+ - test/unit/test_phantomjs_driver.rb
56
64
  - test/unit/test_results.rb
57
- - test/unit/test_rhino_runner.rb
65
+ - test/unit/test_rhino_driver.rb
58
66
  homepage: https://github.com/aaronroyer/qunited
59
67
  licenses: []
60
68
  post_install_message:
@@ -96,5 +104,7 @@ test_files:
96
104
  - test/fixtures/failures_project/test/javascripts/test_basics.js
97
105
  - test/fixtures/failures_project/test/javascripts/test_math.js
98
106
  - test/test_helper.rb
107
+ - test/unit/driver_common_tests.rb
108
+ - test/unit/test_phantomjs_driver.rb
99
109
  - test/unit/test_results.rb
100
- - test/unit/test_rhino_runner.rb
110
+ - test/unit/test_rhino_driver.rb
@@ -1,34 +0,0 @@
1
- module QUnited
2
- module JsRunner
3
- class Base
4
- attr_reader :results
5
-
6
- # Array of file names? Glob pattern?
7
- def initialize(source_files, test_files)
8
- @source_files = if source_files.is_a? String
9
- Dir.glob(source_files)
10
- elsif source_files.is_a? Array
11
- source_files
12
- end
13
-
14
- @test_files = if test_files.is_a? String
15
- Dir.glob(test_files)
16
- elsif test_files.is_a? Array
17
- test_files
18
- end
19
- end
20
-
21
- def can_run?
22
- false
23
- end
24
-
25
- def name
26
- self.class.name.split('::')[-1]
27
- end
28
-
29
- def run
30
- raise 'run not implemented'
31
- end
32
- end
33
- end
34
- end
@@ -1,65 +0,0 @@
1
- require 'tempfile'
2
- require 'fileutils'
3
- require 'yaml'
4
- require 'open3'
5
-
6
- module QUnited
7
- module JsRunner
8
- class Rhino < Base
9
- def can_run?
10
- # TODO: test that you have Java
11
- end
12
-
13
- def run
14
- js_dir = File.expand_path('../rhino/js', __FILE__)
15
-
16
- js_jar, runner = File.join(js_dir, 'js.jar'), File.join(js_dir, 'qunit-runner.js')
17
-
18
- source_files_args = @source_files.map { |sf| %{"#{sf}"} }.join(' ')
19
- test_files_args = @test_files.map { |tf| %{"#{tf}"} }.join(' ')
20
-
21
- tmp_file = Tempfile.new('qunited_results')
22
- tmp_file.close
23
-
24
- cmd = %{java -jar "#{js_jar}" -opt -1 "#{runner}" "#{js_dir}" "#{tmp_file.path}"}
25
- cmd << " #{source_files_args} -- #{test_files_args}"
26
-
27
- # Swallow stdout but allow stderr to get blasted out to console - if there are uncaught
28
- # exceptions or anything else that goes wrong with the JavaScript interpreter the user
29
- # will probably want to know but we are not particularly interested in it.
30
- Open3.popen3(cmd) do |stdin, stdout, stderr|
31
- stdout.each {||} # Ignore; this is just here to make sure we block
32
- # while waiting for tests to finish
33
- unless (err = stderr.read).strip.empty? then $stderr.puts(err) end
34
- end
35
-
36
- @raw_results = clean_up_results(YAML.load(IO.read(tmp_file)))
37
-
38
- @results = ::QUnited::Results.new @raw_results
39
- self
40
- end
41
-
42
- private
43
-
44
- def clean_up_results(results)
45
- results.map! { |mod_results| symbolize_keys mod_results }
46
- results.each do |mod_results|
47
- mod_results[:tests].map! { |test| clean_up_test_results(symbolize_keys(test)) }
48
- end
49
- end
50
-
51
- def clean_up_test_results(test_results)
52
- test_results[:start] = DateTime.parse(test_results[:start])
53
- test_results[:duration] = Float(test_results[:duration])
54
- test_results[:assertion_data].map! { |data| symbolize_keys data }
55
- test_results
56
- end
57
-
58
- def symbolize_keys(hash)
59
- new_hash = {}
60
- hash.keys.each { |key| new_hash[key.to_sym] = hash[key] }
61
- new_hash
62
- end
63
- end
64
- end
65
- end
@@ -1,2 +0,0 @@
1
- require 'qunited/js_runner/base'
2
- require 'qunited/js_runner/rhino'