guard-rackunit 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/ChangeLog +3 -0
- data/LICENSE +674 -0
- data/README.md +82 -0
- data/lib/guard/rackunit.rb +88 -0
- data/lib/guard/rackunit/command.rb +48 -0
- data/lib/guard/rackunit/notifier.rb +35 -0
- data/lib/guard/rackunit/run_result.rb +108 -0
- data/lib/guard/rackunit/runner.rb +61 -0
- data/lib/guard/rackunit/templates/Guardfile +4 -0
- data/lib/guard/rackunit/version.rb +22 -0
- data/spec/lib/guard/rackunit/command_spec.rb +29 -0
- data/spec/lib/guard/rackunit/notifier_spec.rb +16 -0
- data/spec/lib/guard/rackunit/run_result_spec.rb +216 -0
- data/spec/lib/guard/rackunit/runner_spec.rb +84 -0
- data/spec/lib/guard/rackunit_spec.rb +133 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/helpers.rb +86 -0
- data/spec/support/samples/failed_tests +12 -0
- data/spec/support/samples/runtime_error +16 -0
- data/spec/support/samples/success +2 -0
- metadata +135 -0
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
|
3
|
+
`Guard::RackUnit` is a Guard plugin to run
|
4
|
+
[Racket's](http:/racket-lang.org)
|
5
|
+
[RackUnit](http://docs.racket-lang.org/rackunit/index.html) unit
|
6
|
+
tests.
|
7
|
+
|
8
|
+
By default, `Guard::RackUnit` uses the `raco test` command to run
|
9
|
+
tests. Consequently, your RackUnit tests should be placed in
|
10
|
+
Racket test modules: `(module+ test ...)`.
|
11
|
+
|
12
|
+
```
|
13
|
+
guard init rackunit
|
14
|
+
```
|
15
|
+
|
16
|
+
## Setup
|
17
|
+
There are many ways to set up Guard. For the non-Rubyist, the ceremony
|
18
|
+
described below is a typical setup.
|
19
|
+
|
20
|
+
1. Install a relatively recent version of Ruby.
|
21
|
+
2. Install [bundler](http://bundler.io)
|
22
|
+
3. Install [rvm](https://rvm.io)
|
23
|
+
4. Create a `Gemfile` in your Racket project's root directory. To the
|
24
|
+
`Gemfile`, add both the guard gem and the guard-rackunit gem. See
|
25
|
+
the example below.
|
26
|
+
5. In the directory where the `Gemfile` is stored, run `bundle install`
|
27
|
+
|
28
|
+
If everything successfully installs, you are now ready to use
|
29
|
+
Guard. Consult the usage section below on how to use the RackUnit
|
30
|
+
plugin.
|
31
|
+
|
32
|
+
### Example Gemfile
|
33
|
+
``` ruby
|
34
|
+
source 'https://www.rubygems.org'
|
35
|
+
gem "guard", "~> 2.5.1"
|
36
|
+
gem 'guard-rackunit', path: '/home/calbers/src/mine/guard-rackunit'
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
To use the RackUnit Guard plugin, Guard must be initialized. To do so,
|
41
|
+
execute the following command in your project's root directory: `bundle
|
42
|
+
exec guard init`. This should create a `Guardfile` with a default
|
43
|
+
setup for RackUnit.
|
44
|
+
|
45
|
+
Now, to start Guard, execute the following command: `bundle exec guard
|
46
|
+
start`.
|
47
|
+
|
48
|
+
Please consult Guard's own
|
49
|
+
[usage notes](https://github.com/guard/guard#readme) for more
|
50
|
+
information.
|
51
|
+
|
52
|
+
##List of available options:
|
53
|
+
``` ruby
|
54
|
+
test_paths: ['tests/'] # Specify an array of paths that contain unit test files
|
55
|
+
all_on_start: true # Run all the tests at startup, default: false
|
56
|
+
```
|
57
|
+
|
58
|
+
## Support and Issues
|
59
|
+
Please submit support questions, feature requests, and issues to
|
60
|
+
the Github repository's [issue tracker](https://github.com/neomantic/guard-rackunit/issues).
|
61
|
+
|
62
|
+
## Updates
|
63
|
+
Consult the ChangeLog when upgrading to newer versions.
|
64
|
+
|
65
|
+
## Development
|
66
|
+
The source code is hosted at
|
67
|
+
[GitHub](https://github.com/neomantic/guard-rackunit).
|
68
|
+
|
69
|
+
Pull requests are more than welcome. To contribute, please add new
|
70
|
+
unit tests to the existing suite of Ruby
|
71
|
+
[rspec](https://relishapp.com/rspec) unit tests.
|
72
|
+
|
73
|
+
## Requirements
|
74
|
+
1. Racket, with `raco test` support
|
75
|
+
2. Ruby, and the gems which `Guard::RackUnit` depends on.
|
76
|
+
|
77
|
+
## Author
|
78
|
+
[Chad Albers](https://github.com/neomantic)
|
79
|
+
|
80
|
+
## License
|
81
|
+
This Guard plugin is released under the GPLv3. Consult the LICENSE
|
82
|
+
file for more details
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'guard/plugin'
|
2
|
+
# Guard::RackUnit
|
3
|
+
# Copyright (C) 2014 Chad Albers
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
module Guard
|
19
|
+
class RackUnit < Plugin
|
20
|
+
|
21
|
+
require_relative 'rackunit/runner'
|
22
|
+
|
23
|
+
# Initializes a Guard plugin.
|
24
|
+
# Don't do any work here, especially as Guard plugins get initialized even if they are not in an active group!
|
25
|
+
#
|
26
|
+
# @param [Hash] options the custom Guard plugin options
|
27
|
+
# @option options [Array<Guard::Watcher>] watchers the Guard plugin file watchers
|
28
|
+
# @option options [Symbol] group the group this Guard plugin belongs to
|
29
|
+
# @option options [Boolean] any_return allow any object to be returned from a watcher
|
30
|
+
#
|
31
|
+
def initialize(options = {})
|
32
|
+
super
|
33
|
+
@start_on_run = options.delete(:all_on_start) || false
|
34
|
+
@test_paths = options.delete(:test_paths) || []
|
35
|
+
@runner = RackUnit::Runner.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# Called once when Guard starts. Please override initialize method to init stuff.
|
39
|
+
#
|
40
|
+
# @raise [:task_has_failed] when start has failed
|
41
|
+
# @return [Object] the task result
|
42
|
+
#
|
43
|
+
def start
|
44
|
+
::Guard::UI.info 'Guard::RackUnit is running'
|
45
|
+
return run_all if @start_on_run
|
46
|
+
pending_result
|
47
|
+
end
|
48
|
+
|
49
|
+
# Called when just `enter` is pressed
|
50
|
+
# This method should be principally used for long action like running all specs/tests/...
|
51
|
+
#
|
52
|
+
# @raise [:task_has_failed] when run_all has failed
|
53
|
+
# @return [Object] the task result
|
54
|
+
#
|
55
|
+
def run_all
|
56
|
+
return pending_result unless test_paths?
|
57
|
+
Guard::UI.info("Resetting", reset: true)
|
58
|
+
do_run{ @runner.run(@test_paths) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Called on file(s) modifications that the Guard plugin watches.
|
62
|
+
#
|
63
|
+
# @param [Array<String>] paths the changes files or paths
|
64
|
+
# @raise [:task_has_failed] when run_on_modifications has failed
|
65
|
+
# @return [Object] the task result
|
66
|
+
#
|
67
|
+
def run_on_modifications(paths)
|
68
|
+
return pending_result if paths.empty?
|
69
|
+
Guard::UI.info("Running: #{paths.join(', ')}", reset: true)
|
70
|
+
do_run{ @runner.run(paths) }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def do_run
|
75
|
+
run_result = yield
|
76
|
+
run_result.issue_notification
|
77
|
+
run_result.successful? ? run_result : throw(:task_has_failed)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_paths?
|
81
|
+
!@test_paths.nil? && !@test_paths.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def pending_result
|
85
|
+
@pending ||= RunResult::Pending.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
# Copyright (C) 2014 Chad Albers
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'open3'
|
18
|
+
require_relative './run_result'
|
19
|
+
module Guard
|
20
|
+
class RackUnit
|
21
|
+
class Command
|
22
|
+
|
23
|
+
def execute(paths)
|
24
|
+
return RunResult::Pending.new if paths.empty?
|
25
|
+
cmd = sprintf('%s %s', DEFAULT_CMD_STR, paths.join(' '))
|
26
|
+
with_environment do
|
27
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
28
|
+
pid = wait_thr.pid
|
29
|
+
wait_thr.value
|
30
|
+
RunResult.create(stdout, stderr)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_environment
|
36
|
+
if defined?(::Bundler)
|
37
|
+
::Bundler.with_clean_env { yield }
|
38
|
+
else
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
DEFAULT_CMD_STR = 'raco test'.freeze
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
# Copyright (C) 2014 Chad Albers
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
module Guard
|
18
|
+
class RackUnit
|
19
|
+
class Notifier
|
20
|
+
|
21
|
+
DEFAULT_OPTIONS = {
|
22
|
+
title: 'RackUnit Results',
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def initialize(message)
|
26
|
+
@message = message
|
27
|
+
end
|
28
|
+
|
29
|
+
def notify(options)
|
30
|
+
::Guard::Notifier.notify(@message,
|
31
|
+
DEFAULT_OPTIONS.merge(options))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
# Copyright (C) 2014 Chad Albers
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'set'
|
18
|
+
require_relative './notifier'
|
19
|
+
module Guard
|
20
|
+
class RackUnit
|
21
|
+
class RunResult
|
22
|
+
|
23
|
+
def self.create(stdout, stderr)
|
24
|
+
err_byte = stderr.getbyte
|
25
|
+
if err_byte.nil?
|
26
|
+
Success.new(stdout)
|
27
|
+
else
|
28
|
+
puts stdout.readlines
|
29
|
+
stderr.ungetbyte(err_byte)
|
30
|
+
Failure.new(stderr)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Pending
|
35
|
+
def issue_notification; end
|
36
|
+
def paths; Set[]; end
|
37
|
+
def successful?; false; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Success
|
41
|
+
|
42
|
+
def initialize(result_io)
|
43
|
+
@message = "Success"
|
44
|
+
result_io.each_line do |line|
|
45
|
+
puts @message = line
|
46
|
+
end
|
47
|
+
@message.chomp!
|
48
|
+
end
|
49
|
+
|
50
|
+
def issue_notification
|
51
|
+
Notifier.new(@message).notify NOTIFY_OPTIONS
|
52
|
+
end
|
53
|
+
|
54
|
+
def paths; Set[]; end
|
55
|
+
|
56
|
+
def successful?; true; end
|
57
|
+
|
58
|
+
private
|
59
|
+
NOTIFY_OPTIONS = {image: :success, priority: -2}.freeze
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
class Failure
|
65
|
+
|
66
|
+
def initialize(stderr)
|
67
|
+
@message = "Failed"
|
68
|
+
@failed_paths_set = Set[]
|
69
|
+
stderr.each_line do |line|
|
70
|
+
puts line # give user some feedback
|
71
|
+
|
72
|
+
if line =~ ERROR_REGEX
|
73
|
+
# an exception as raised
|
74
|
+
@message = stderr.readline
|
75
|
+
puts @message
|
76
|
+
@message = "ERROR: #{@message.strip}"
|
77
|
+
stderr.each_line{|line| puts line}
|
78
|
+
else
|
79
|
+
match_data = line.match(FAILURE_REGEX)
|
80
|
+
unless match_data.nil?
|
81
|
+
@failed_paths_set.add(match_data[1])
|
82
|
+
else
|
83
|
+
# the last line should have the results summary
|
84
|
+
@message = line
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@message.chomp!
|
89
|
+
end
|
90
|
+
|
91
|
+
def issue_notification
|
92
|
+
Notifier.new(@message).notify NOTIFY_OPTIONS
|
93
|
+
end
|
94
|
+
|
95
|
+
def paths
|
96
|
+
@failed_paths_set || Set[]
|
97
|
+
end
|
98
|
+
|
99
|
+
def successful?; false; end
|
100
|
+
|
101
|
+
private
|
102
|
+
FAILURE_REGEX = /\Alocation:.+path:(.+)>/
|
103
|
+
NOTIFY_OPTIONS = {image: :failed, priority: 2}.freeze
|
104
|
+
ERROR_REGEX = /context#{Regexp.escape('...')}:/
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
# Copyright (C) 2014 Chad Albers
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require_relative './run_result'
|
18
|
+
require_relative './command'
|
19
|
+
|
20
|
+
module Guard
|
21
|
+
class RackUnit
|
22
|
+
class Runner
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@last_run_result = pending_result
|
26
|
+
end
|
27
|
+
|
28
|
+
# This class runs a Command (which will be customizable)
|
29
|
+
# And produces a run result
|
30
|
+
def run(paths = [])
|
31
|
+
return pending_result if paths.empty?
|
32
|
+
path_set = PathSet.new(@last_run_result.paths, Set.new(paths))
|
33
|
+
@last_run_result = Command.new.execute(path_set.to_a)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def pending_result
|
38
|
+
RunResult::Pending.new
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
class PathSet
|
46
|
+
|
47
|
+
def initialize(previous_set, new_set)
|
48
|
+
@set = previous_set | select_existing(new_set)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_a
|
52
|
+
@set.to_a
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def select_existing(paths)
|
57
|
+
paths.select{|path| File.exists?(path)}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Guard::RackUnit
|
2
|
+
# Copyright (C) 2014 Chad Albers
|
3
|
+
#
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
|
17
|
+
require 'guard'
|
18
|
+
module Guard
|
19
|
+
class RackUnit
|
20
|
+
VERSION = '1.0.0'
|
21
|
+
end
|
22
|
+
end
|