loris 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +2 -0
  2. data/README.rdoc +9 -0
  3. data/Rakefile +41 -0
  4. data/TODO +21 -0
  5. data/VERSION +1 -0
  6. data/bin/loris +15 -0
  7. data/cucumber.yml +1 -0
  8. data/examples/self_test/jsl.conf +1 -0
  9. data/examples/self_test/spec/spec.rhino.js +6 -0
  10. data/features/javascript_lint.feature +34 -0
  11. data/features/run.feature +36 -0
  12. data/features/step_definitons/all.rb +69 -0
  13. data/features/support/env.rb +145 -0
  14. data/lib/always_continuer.rb +7 -0
  15. data/lib/browser_finder.rb +23 -0
  16. data/lib/file_actioner.rb +16 -0
  17. data/lib/file_finder.rb +31 -0
  18. data/lib/filters/ends_with_filter.rb +17 -0
  19. data/lib/filters/extension_filter.rb +20 -0
  20. data/lib/filters/file_filter.rb +14 -0
  21. data/lib/filters/modified_filter.rb +21 -0
  22. data/lib/icons/error.png +0 -0
  23. data/lib/icons/failure.png +0 -0
  24. data/lib/icons/info.png +0 -0
  25. data/lib/icons/success.png +0 -0
  26. data/lib/icons/warning.png +0 -0
  27. data/lib/js-test-driver/JsTestDriver-1.1.jar +0 -0
  28. data/lib/js-test-driver/plugins/coverage-1.1.jar +0 -0
  29. data/lib/loris.rb +170 -0
  30. data/lib/outputs/growl_output.rb +26 -0
  31. data/lib/outputs/output_collection.rb +23 -0
  32. data/lib/outputs/shell_output.rb +17 -0
  33. data/lib/outputs/unix_console_clearing_output.rb +13 -0
  34. data/lib/outputs/windows_console_clearing_output.rb +13 -0
  35. data/lib/pinger.rb +23 -0
  36. data/lib/poller.rb +16 -0
  37. data/lib/sleep_waiter.rb +11 -0
  38. data/lib/task_manager.rb +45 -0
  39. data/lib/tasks/command_line_task.rb +28 -0
  40. data/lib/tasks/javascript_lint/javascript_lint_parser.rb +45 -0
  41. data/lib/tasks/javascript_lint/javascript_lint_runner.rb +25 -0
  42. data/lib/tasks/js_test_driver/js_test_driver_config.rb +22 -0
  43. data/lib/tasks/js_test_driver/js_test_driver_parser.rb +28 -0
  44. data/lib/tasks/js_test_driver/js_test_driver_runner.rb +28 -0
  45. data/lib/tasks/js_test_driver/js_test_driver_server.rb +38 -0
  46. data/lib/tasks/jspec/jspec_parser.rb +30 -0
  47. data/lib/tasks/jspec/jspec_runner.rb +25 -0
  48. data/lib/tasks/list_task.rb +34 -0
  49. data/lib/tasks/rspec/rspec_parser.rb +25 -0
  50. data/lib/tasks/rspec/rspec_runner.rb +25 -0
  51. data/lib/unix_process.rb +7 -0
  52. data/lib/windows_process.rb +12 -0
  53. data/loris.gemspec +133 -0
  54. data/loris.tmproj +232 -0
  55. data/spec/file_actioner_spec.rb +41 -0
  56. data/spec/file_finder_spec.rb +73 -0
  57. data/spec/filters/ends_with_filter_spec.rb +26 -0
  58. data/spec/filters/file_filter_spec.rb +28 -0
  59. data/spec/filters/modified_filter_spec.rb +47 -0
  60. data/spec/growl_output_spec.rb +33 -0
  61. data/spec/list_task_spec.rb +58 -0
  62. data/spec/poller_spec.rb +28 -0
  63. data/spec/shell_output_spec.rb +25 -0
  64. data/spec/task_manager_spec.rb +64 -0
  65. data/spec/tasks/javascript_lint/javascript_lint_runner_spec.rb +90 -0
  66. data/spec/tasks/js_test_driver/js_test_driver_runner_spec.rb +92 -0
  67. data/spec/tasks/jspec/jspec_parser_spec.rb +28 -0
  68. data/spec/tasks/jspec/jspec_runner_spec.rb +78 -0
  69. metadata +174 -0
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .gitignore
2
+ .DS_Store
data/README.rdoc ADDED
@@ -0,0 +1,9 @@
1
+ == Autotest for Javascript ==
2
+
3
+ Run your javascript unit tests any time a file in your project changes.
4
+
5
+ Runs Javascript Lint (if jsl.conf is found)
6
+ Runs JSpec (if spec/spec.rhino.js is found)
7
+ Runs JS Test Driver (if jsTestDriver.conf is found)
8
+
9
+ Also runs RSpec (if any file ending in _spec.rb is found in the spec directory)
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "loris"
11
+ gem.summary = 'Automatically run javascript unit tests'
12
+ gem.description = 'Automatically run javascript unit tests'
13
+ gem.email = "loris@monket.net"
14
+ gem.homepage = "http://github.com/karl/loris"
15
+ gem.authors = ["Karl O'Keeffe"]
16
+
17
+ gem.add_dependency('visionmedia-bind', [">= 0.2.6"])
18
+ gem.add_dependency('karl-growl', [">= 1.0.3"])
19
+ gem.add_dependency('extensions', [">= 0.6.0"])
20
+ gem.add_dependency('win32-process', [">= 0.6.1"])
21
+
22
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
23
+ end
24
+ rescue LoadError
25
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
26
+ end
27
+
28
+
29
+ task :default => :spec
30
+
31
+ Spec::Rake::SpecTask.new(:spec) do |t|
32
+ t.spec_files = Dir.glob('spec/**/*_spec.rb')
33
+ # t.rcov = true
34
+ end
35
+
36
+ Cucumber::Rake::Task.new(:features) do |t|
37
+ t.cucumber_opts = "features --format pretty"
38
+ end
39
+
40
+
41
+
data/TODO ADDED
@@ -0,0 +1,21 @@
1
+ * Work out what is going on with fork in windows (running second copy of Loris?)
2
+
3
+ * Remove JsTestDriver.jar from lib dir
4
+ * Tidy Windows related if statements
5
+ * Tidy JSL filename removing
6
+
7
+ * javascript lint not installed? (package if not already installed?)
8
+ * JSpec not installed? (can require as gem)
9
+
10
+ * Detect file rename
11
+ * Detect file deletion/directory rename
12
+
13
+ * Factories to create tasks
14
+
15
+ * Deal with filesystem exceptions
16
+ * jspec task (just modified files?)
17
+ * javascript lint task (just modified files?)
18
+ * separate thread (create new and kill old)
19
+ * listen for interrupt
20
+ * cucumber task
21
+ * Tidy growl windows code
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.12
data/bin/loris ADDED
@@ -0,0 +1,15 @@
1
+ #!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby
2
+ $:.unshift(File.dirname(__FILE__ + '.rb') + '/../lib') unless $:.include?(File.dirname(__FILE__ + '.rb') + '/../lib')
3
+
4
+ require 'loris'
5
+ begin
6
+ # The dup is to keep ARGV intact, so that tools like ruby-debug can respawn.
7
+ failure = Loris::CLI::Main.execute(ARGV.dup)
8
+ Kernel.exit(failure ? 1 : 0)
9
+ rescue SystemExit => e
10
+ Kernel.exit(e.status)
11
+ rescue Exception => e
12
+ STDERR.puts("#{e.message} (#{e.class})")
13
+ STDERR.puts(e.backtrace.join("\n"))
14
+ Kernel.exit 1
15
+ end
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: features
@@ -0,0 +1 @@
1
+ +process tmp/*.js
@@ -0,0 +1,6 @@
1
+
2
+ load('/Library/Ruby/Gems/1.8/gems/visionmedia-jspec-2.2.1/lib/jspec.js')
3
+
4
+ JSpec
5
+ .run({ formatter : JSpec.formatters.Terminal })
6
+ .report()
@@ -0,0 +1,34 @@
1
+ Feature Javascript Lint
2
+
3
+ Scenario: Javascript Lint is run
4
+ Given I run loris --debug
5
+ When I create a file named "example.js" with:
6
+ """
7
+ var foo = function() {
8
+ x = 'moo'
9
+ }
10
+ """
11
+ And I wait until loris has finished processing changes
12
+ Then the Loris output should contain:
13
+ """
14
+ Javascript Lint
15
+ warning
16
+ """
17
+ And I should not see any errors
18
+
19
+ # Scenario: Javascript Lint is run when Loris starts
20
+ # When I create a file named "example.js" with:
21
+ # """
22
+ # var foo = function() {
23
+ # x = 'moo'
24
+ # }
25
+ # """
26
+ # Given I run loris --debug
27
+ # And I wait until loris has finished processing changes
28
+ # Then the Loris output should contain:
29
+ # """
30
+ # Javascript Lint
31
+ # warning
32
+ # """
33
+ # And I should not see any errors
34
+ #
@@ -0,0 +1,36 @@
1
+ Feature: Run Loris
2
+
3
+ Scenario: Creating a file triggers loris
4
+ Given I run loris --debug
5
+ When I create a file named "created.txt"
6
+ And I wait until loris has finished processing changes
7
+ Then I should see "created.txt" in the Loris output
8
+ And I should not see any errors
9
+
10
+ Scenario: Modifying a file triggers loris
11
+ Given I create a file named "modified.txt"
12
+ When I run loris --debug
13
+ And I wait until loris has finished processing changes
14
+ And I start recording the Loris output
15
+ And I modify the "modified.txt" file
16
+ And I wait until loris has finished processing changes
17
+ Then I should see "modified.txt" in the recorded output
18
+ And I should not see any errors
19
+
20
+ Scenario: Modified only triggered once
21
+ Given I create a file named "modified.txt"
22
+ When I run loris --debug
23
+ And I wait until loris has finished processing changes
24
+ And I start recording the Loris output
25
+ And I modify the "modified.txt" file
26
+ And I wait until loris has finished processing changes
27
+ And I wait until loris has finished processing changes
28
+ Then I should only see "modified.txt" once in the recorded output
29
+ And I should not see any errors
30
+
31
+ Scenario: Directories not included
32
+ Given I run loris --debug
33
+ When I create a directory named "dir"
34
+ And I wait until loris has finished processing changes
35
+ Then I should NOT see "dir" in the Loris output
36
+ And I should not see any errors
@@ -0,0 +1,69 @@
1
+ require 'spec/expectations'
2
+
3
+ When /^I run loris(.*)$/ do |loris_opts|
4
+ run_in_background "#{Loris::RUBY_BINARY} #{Loris::BINARY} #{loris_opts}"
5
+ end
6
+
7
+ Given /^(?:I create )a file named "([^\"]*)"$/ do |file_name|
8
+ @current_dir = working_dir
9
+ create_file(file_name, '')
10
+ end
11
+
12
+ Given /^(?:I create )a directory named "([^\"]*)"$/ do |dir_name|
13
+ @current_dir = working_dir
14
+ create_dir(dir_name)
15
+ end
16
+
17
+ Given /^(?:I create )a file named "([^\"]*)" with:$/ do |file_name, file_content|
18
+ @current_dir = working_dir
19
+ create_file(file_name, file_content)
20
+ end
21
+
22
+ When /^I modify the "([^\"]*)" file$/ do |file_name|
23
+ @current_dir = working_dir
24
+ touch_file(file_name)
25
+ end
26
+
27
+ When /^I wait until loris has finished processing changes$/ do
28
+ len = get_background_output.length
29
+ new_output = ""
30
+ while not new_output =~ /\[Poll complete\]/
31
+ new_output = get_background_output[len..-1]
32
+ sleep 0.5
33
+ end
34
+ end
35
+
36
+ When /^I start recording the Loris output$/ do
37
+ @pre_recorded_length = get_background_output.length
38
+ end
39
+
40
+ Then /^I should see "([^\"]*)" in the recorded output$/ do |text|
41
+ recorded = get_background_output[@pre_recorded_length..-1]
42
+ recorded.should include text
43
+ end
44
+
45
+ Then /^I should only see "([^\"]*)" once in the recorded output$/ do |text|
46
+ recorded = get_background_output[@pre_recorded_length..-1]
47
+ recorded.should include text
48
+ recorded.scan(text).length.should equal 1
49
+ end
50
+
51
+ Then /^I should see "([^\"]*)" in the Loris output$/ do |text|
52
+ get_background_output.should include text
53
+ end
54
+
55
+ Then /^the Loris output should contain:$/ do |text|
56
+ get_background_output.should include text
57
+ end
58
+
59
+ Then /^the Loris output should NOT contain:$/ do |text|
60
+ get_background_output.should_not include text
61
+ end
62
+
63
+ Then /^I should NOT see "([^\"]*)" in the Loris output$/ do |text|
64
+ get_background_output.should_not include text
65
+ end
66
+
67
+ Then /^I should not see any errors$/ do
68
+ get_background_error.strip.should == ""
69
+ end
@@ -0,0 +1,145 @@
1
+ require 'rubygems'
2
+ require 'tempfile'
3
+ require 'spec/expectations'
4
+ require 'fileutils'
5
+ require 'forwardable'
6
+
7
+ require 'lib/loris.rb'
8
+
9
+ class CucumberWorld
10
+ extend Forwardable
11
+ def_delegators CucumberWorld, :examples_dir, :self_test_dir, :working_dir, :cucumber_lib_dir
12
+
13
+ def self.examples_dir(subdir=nil)
14
+ @examples_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../examples'))
15
+ subdir ? File.join(@examples_dir, subdir) : @examples_dir
16
+ end
17
+
18
+ def self.self_test_dir
19
+ @self_test_dir ||= examples_dir('self_test')
20
+ end
21
+
22
+ def self.working_dir
23
+ @working_dir ||= examples_dir('self_test/tmp')
24
+ end
25
+
26
+ def cucumber_lib_dir
27
+ @cucumber_lib_dir ||= File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))
28
+ end
29
+
30
+ def initialize
31
+ @current_dir = self_test_dir
32
+ end
33
+
34
+ private
35
+ attr_reader :last_exit_status, :last_stderr
36
+
37
+ # The last standard out, with the duration line taken out (unpredictable)
38
+ def last_stdout
39
+ strip_duration(@last_stdout)
40
+ end
41
+
42
+ def strip_duration(s)
43
+ s.gsub(/^\d+m\d+\.\d+s\n/m, "")
44
+ end
45
+
46
+ def replace_duration(s, replacement)
47
+ s.gsub(/\d+m\d+\.\d+s/m, replacement)
48
+ end
49
+
50
+ def create_file(file_name, file_content)
51
+ file_content.gsub!("CUCUMBER_LIB", "'#{cucumber_lib_dir}'") # Some files, such as Rakefiles need to use the lib dir
52
+ in_current_dir do
53
+ File.open(file_name, 'w') { |f| f << file_content }
54
+ end
55
+ end
56
+
57
+ def create_dir(dir_name)
58
+ in_current_dir do
59
+ # create any subdirectories if needed
60
+ FileUtils.mkdir(dir_name)
61
+ end
62
+ end
63
+
64
+ def touch_file(file_name)
65
+ in_current_dir do
66
+ FileUtils.touch(file_name)
67
+ end
68
+ end
69
+
70
+ def background_jobs
71
+ @background_jobs ||= []
72
+ end
73
+
74
+ def in_current_dir(&block)
75
+ Dir.chdir(@current_dir, &block)
76
+ end
77
+
78
+ def run(command)
79
+ stderr_file = Tempfile.new('loris-stderr')
80
+ stderr_file.close
81
+
82
+ stdout_file = Tempfile.new('loris-stdout')
83
+ stdout_file.close
84
+
85
+ in_current_dir do
86
+ `#{command} 1> #{stdout_file.path} 2> #{stderr_file.path}`
87
+ @last_exit_status = $?.exitstatus
88
+ end
89
+ @last_stdout = IO.read(stdout_file.path)
90
+ @last_stderr = IO.read(stderr_file.path)
91
+ end
92
+
93
+ def run_in_background(command)
94
+ @stderr_file = Tempfile.new('loris-stderr')
95
+ @stderr_file.close
96
+
97
+ @stdout_file = Tempfile.new('loris-stdout')
98
+ @stdout_file.close
99
+
100
+ pid = fork
101
+ in_current_dir do
102
+ if pid
103
+ background_jobs << pid
104
+ else
105
+ cmd = "#{command} 1> #{@stdout_file.path} 2> #{@stderr_file.path}"
106
+ exec cmd
107
+ end
108
+ end
109
+ end
110
+
111
+ def terminate_background_jobs
112
+ if @background_jobs
113
+ @background_jobs.each do |pid|
114
+ Process.kill(Signal.list['TERM'], pid)
115
+ end
116
+ end
117
+ end
118
+
119
+ def get_background_output
120
+ return IO.read(@stdout_file.path) rescue return ""
121
+ end
122
+
123
+ def get_background_error
124
+ return IO.read(@stderr_file.path) rescue return ""
125
+ end
126
+
127
+ end
128
+
129
+ World do
130
+ CucumberWorld.new
131
+ end
132
+
133
+ Before do
134
+ FileUtils.rm_rf CucumberWorld.working_dir
135
+ FileUtils.mkdir CucumberWorld.working_dir
136
+ end
137
+
138
+ After do
139
+ err = get_background_error
140
+ if (err != "")
141
+ puts 'Background process errors:'
142
+ puts err
143
+ end
144
+ terminate_background_jobs
145
+ end
@@ -0,0 +1,7 @@
1
+ class AlwaysContinuer
2
+
3
+ def continue?
4
+ return true
5
+ end
6
+
7
+ end
@@ -0,0 +1,23 @@
1
+ require 'win32/registry'
2
+
3
+ class BrowserFinder
4
+
5
+ def initialize
6
+ @ie = '"C:/Program Files/Internet Explorer/iexplore.exe" "%1"'
7
+ end
8
+
9
+ def getDefault
10
+ browser = getBrowserFromRegistry()
11
+ return browser != '' ? browser : @ie
12
+ end
13
+
14
+ def getBrowserFromRegistry
15
+
16
+ Win32::Registry::HKEY_CLASSES_ROOT.open('http\shell\open\command') do |reg|
17
+ reg_typ, reg_val = reg.read('')
18
+ return reg_val
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ class FileActioner
2
+
3
+ def initialize(file_finder, task_manager)
4
+ @file_finder = file_finder
5
+ @task_manager = task_manager
6
+ end
7
+
8
+ def run
9
+ files = @file_finder.find
10
+
11
+ if (files[:filtered] != [])
12
+ @task_manager.run(files)
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,31 @@
1
+ class FileFinder
2
+
3
+ def initialize(finder, dir)
4
+ @finder = finder
5
+ @dir = dir
6
+ @filters = []
7
+ end
8
+
9
+ def add_filter(filter)
10
+ @filters << filter
11
+ end
12
+
13
+ def find
14
+ all_files = []
15
+ filtered_files = []
16
+
17
+ @finder.find(@dir) do |path|
18
+ all_files << path
19
+
20
+ keep = @filters.inject(true) { |k, filter| k && filter.filter(path) }
21
+ filtered_files << path if keep
22
+ end
23
+
24
+ @filters.each do |filter|
25
+ filter.complete
26
+ end
27
+
28
+ return { :all => all_files, :filtered => filtered_files }
29
+ end
30
+
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'extensions/string'
3
+
4
+ class EndsWithFilter
5
+
6
+ def initialize(text)
7
+ @text = text.downcase
8
+ end
9
+
10
+ def filter(file_name)
11
+ return file_name.downcase.ends_with? @text
12
+ end
13
+
14
+ def complete
15
+ end
16
+
17
+ end
@@ -0,0 +1,20 @@
1
+ class ExtensionFilter
2
+
3
+ def initialize(file_class, extension)
4
+ @file_class = file_class
5
+ @extension = extension.downcase
6
+ end
7
+
8
+ def filter(file_name)
9
+ return file_type(file_name) == @extension
10
+ end
11
+
12
+ # Return the part of the file name string after the last '.'
13
+ def file_type(file_name)
14
+ @file_class.extname(file_name).gsub( /^\./, '' ).downcase
15
+ end
16
+
17
+ def complete
18
+ end
19
+
20
+ end
@@ -0,0 +1,14 @@
1
+ class FileFilter
2
+
3
+ def initialize(file_class)
4
+ @file_class = file_class
5
+ end
6
+
7
+ def filter(path)
8
+ return @file_class.file?(path)
9
+ end
10
+
11
+ def complete
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ class ModifiedFilter
2
+
3
+ def initialize(file_class, last_modified = nil)
4
+ @file_class = file_class
5
+ @last_modified = last_modified
6
+ @modifieds = []
7
+ end
8
+
9
+ def filter(path)
10
+ modified = @file_class.mtime(path)
11
+ @modifieds << modified
12
+
13
+ return @last_modified.nil? || modified > @last_modified
14
+ end
15
+
16
+ def complete
17
+ @last_modified = @modifieds.max
18
+ @modifieds = []
19
+ end
20
+
21
+ end
Binary file
Binary file
Binary file
Binary file
Binary file