zeus-edge 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/MIT-LICENSE +22 -0
- data/Rakefile +57 -0
- data/bin/zeus +16 -0
- data/build/fsevents-wrapper +0 -0
- data/build/zeus-darwin-amd64 +0 -0
- data/build/zeus-linux-386 +0 -0
- data/build/zeus-linux-amd64 +0 -0
- data/ext/inotify-wrapper/extconf.rb +24 -0
- data/ext/inotify-wrapper/inotify-wrapper.cpp +116 -0
- data/lib/zeus/load_tracking.rb +53 -0
- data/lib/zeus/m/test_collection.rb +52 -0
- data/lib/zeus/m/test_method.rb +35 -0
- data/lib/zeus/m.rb +346 -0
- data/lib/zeus/plan.rb +0 -0
- data/lib/zeus/rails.rb +220 -0
- data/lib/zeus.rb +161 -0
- data/man/build/zeus +61 -0
- data/man/build/zeus-init +13 -0
- data/man/build/zeus-init.txt +17 -0
- data/man/build/zeus-start +16 -0
- data/man/build/zeus-start.txt +18 -0
- data/man/build/zeus.txt +65 -0
- data/spec/fake_mini_test.rb +31 -0
- data/spec/m_spec.rb +83 -0
- data/spec/spec_helper.rb +38 -0
- data/zeus.gemspec +33 -0
- metadata +109 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Burke Libbey
|
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/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
ROOT_PATH = Pathname.new(File.expand_path("../../", __FILE__))
|
7
|
+
RUBYGEM_PATH = Pathname.new(File.expand_path("../", __FILE__))
|
8
|
+
|
9
|
+
task build: [:import_externals, :version, :manifest]
|
10
|
+
task default: :build
|
11
|
+
|
12
|
+
task :import_externals do
|
13
|
+
puts "building rubygem"
|
14
|
+
|
15
|
+
Rake::Task[:clean].invoke
|
16
|
+
|
17
|
+
FileUtils.rm_r(RUBYGEM_PATH + "man") rescue nil
|
18
|
+
FileUtils.rm_r(RUBYGEM_PATH + "build") rescue nil
|
19
|
+
|
20
|
+
# manpages
|
21
|
+
FileUtils.mkdir(RUBYGEM_PATH + "man")
|
22
|
+
FileUtils.cp_r(ROOT_PATH + "man/build", RUBYGEM_PATH + "man")
|
23
|
+
|
24
|
+
# multi-arch binaries
|
25
|
+
FileUtils.cp_r(ROOT_PATH + "build", RUBYGEM_PATH + "build")
|
26
|
+
|
27
|
+
Rake::Task[:manifest].invoke
|
28
|
+
Rake::Task[:build].invoke
|
29
|
+
end
|
30
|
+
|
31
|
+
task :version do
|
32
|
+
version = File.read('../VERSION').chomp
|
33
|
+
File.open('lib/zeus/version.rb', 'w') { |f| f.puts <<END
|
34
|
+
module Zeus
|
35
|
+
VERSION = "#{version}"
|
36
|
+
end
|
37
|
+
END
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
task :manifest do
|
42
|
+
files = `find . -type f | sed 's|^\./||'`.lines.map(&:chomp)
|
43
|
+
exceptions = [
|
44
|
+
/.gitignore$/,
|
45
|
+
/^MANIFEST$/,
|
46
|
+
/^pkg\//,
|
47
|
+
]
|
48
|
+
files.reject! { |f| exceptions.any? {|ex| f =~ ex }}
|
49
|
+
File.open('MANIFEST', 'w') {|f| f.puts files.join("\n") }
|
50
|
+
end
|
51
|
+
|
52
|
+
task :clean do
|
53
|
+
FileUtils.rm(RUBYGEM_PATH + "lib/zeus/version.rb") rescue nil
|
54
|
+
FileUtils.rm_r(RUBYGEM_PATH + "man") rescue nil
|
55
|
+
FileUtils.rm_r(RUBYGEM_PATH + "build") rescue nil
|
56
|
+
end
|
57
|
+
|
data/bin/zeus
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
platform = `uname -sm`
|
2
|
+
|
3
|
+
exe = case platform
|
4
|
+
when /^Darwin/ ; "zeus-darwin-amd64"
|
5
|
+
when /^Linux.*64/ ; "zeus-linux-amd64"
|
6
|
+
when /^Linux.*/ ; "zeus-linux-386"
|
7
|
+
else
|
8
|
+
puts "Zeus is not supported on your platform."
|
9
|
+
puts "It's not likely to ever be possible on Windows."
|
10
|
+
puts "If you're using another platform that you think should work easily, open an issue at:"
|
11
|
+
puts "https://github.com/burke/zeus/issues"
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
zeusgemdir = File.expand_path("../../", __FILE__)
|
16
|
+
exec "#{zeusgemdir}/build/#{exe}", *ARGV
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,24 @@
|
|
1
|
+
if /linux/ =~ RUBY_PLATFORM
|
2
|
+
open("Makefile", "wb") do |f|
|
3
|
+
f.write <<-EOF
|
4
|
+
CXX = g++
|
5
|
+
CXXFLAGS = -O3 -g -Wall
|
6
|
+
|
7
|
+
inotify-wrapper: inotify-wrapper.o
|
8
|
+
$(CXX) $(CXXFLAGS) $< -o $@
|
9
|
+
|
10
|
+
%.o: %.cpp
|
11
|
+
$(CXX) $(CXXFLAGS) -c $< -o $@
|
12
|
+
|
13
|
+
install:
|
14
|
+
# do nothing
|
15
|
+
EOF
|
16
|
+
end
|
17
|
+
else
|
18
|
+
open("Makefile", "wb") do |f|
|
19
|
+
f.write <<-EOF
|
20
|
+
install:
|
21
|
+
# do nothing
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#include <map>
|
2
|
+
#include <string>
|
3
|
+
|
4
|
+
#include <stdio.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <errno.h>
|
8
|
+
#include <unistd.h>
|
9
|
+
#include <sys/types.h>
|
10
|
+
#include <sys/inotify.h>
|
11
|
+
|
12
|
+
#include <errno.h>
|
13
|
+
|
14
|
+
#define EVENT_SIZE (sizeof (struct inotify_event))
|
15
|
+
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
|
16
|
+
|
17
|
+
using namespace std;
|
18
|
+
|
19
|
+
static int _inotify_fd;
|
20
|
+
static map<int, string> _WatchedFiles;
|
21
|
+
static map<string, bool> _FileIsWatched;
|
22
|
+
|
23
|
+
// static int inotifyFlags = IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF;
|
24
|
+
static int inotifyFlags = IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF;
|
25
|
+
|
26
|
+
void maybeAddFileToWatchList(string file)
|
27
|
+
{
|
28
|
+
if (_FileIsWatched[file]) return;
|
29
|
+
|
30
|
+
int wd = inotify_add_watch(_inotify_fd, file.c_str(), inotifyFlags);
|
31
|
+
int attempts = 0;
|
32
|
+
// Files are momentarily inaccessible when they are rewritten. I couldn't
|
33
|
+
// find a good way to deal with this, so we poll 'deleted' files for 0.25s or so
|
34
|
+
// to see if they reappear.
|
35
|
+
while (wd == -1 && errno == ENOENT) {
|
36
|
+
usleep(10000);
|
37
|
+
wd = inotify_add_watch(_inotify_fd, file.c_str(), inotifyFlags);
|
38
|
+
if (attempts++ == 25) break; // try for at most about a quarter of a second
|
39
|
+
}
|
40
|
+
if (wd != -1) {
|
41
|
+
_WatchedFiles[wd] = file;
|
42
|
+
_FileIsWatched[file] = true;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
// This essentially removes a file from the watchlist then
|
47
|
+
// immediately re-adds it. This is because when a file is rewritten,
|
48
|
+
// as so many editors love to do, the watchdescriptor no longer refers to
|
49
|
+
// the file, so re must re-watch the path.
|
50
|
+
void replaceFileInWatchList(int wd, string file)
|
51
|
+
{
|
52
|
+
_FileIsWatched.erase(file);
|
53
|
+
_WatchedFiles.erase(wd);
|
54
|
+
inotify_rm_watch(_inotify_fd, wd);
|
55
|
+
maybeAddFileToWatchList(file);
|
56
|
+
}
|
57
|
+
|
58
|
+
void handleStdin()
|
59
|
+
{
|
60
|
+
char line[2048];
|
61
|
+
if (fgets(line, sizeof(line), stdin) == NULL) return;
|
62
|
+
line[strlen(line)-1] = 0;
|
63
|
+
|
64
|
+
maybeAddFileToWatchList(string(line));
|
65
|
+
}
|
66
|
+
|
67
|
+
void handleInotify()
|
68
|
+
{
|
69
|
+
int length;
|
70
|
+
int i = 0;
|
71
|
+
char buffer[EVENT_BUF_LEN];
|
72
|
+
string filename;
|
73
|
+
|
74
|
+
length = read(_inotify_fd, buffer, EVENT_BUF_LEN);
|
75
|
+
if (length < 0) return;
|
76
|
+
|
77
|
+
while (i < length) {
|
78
|
+
struct inotify_event *event = (struct inotify_event *) &buffer[i];
|
79
|
+
string file = _WatchedFiles[event->wd];
|
80
|
+
if (file != "") {
|
81
|
+
printf("%s\n", file.c_str());
|
82
|
+
fflush(stdout);
|
83
|
+
replaceFileInWatchList(event->wd, file);
|
84
|
+
}
|
85
|
+
|
86
|
+
i += EVENT_SIZE + event->len;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
void go()
|
91
|
+
{
|
92
|
+
fd_set rfds;
|
93
|
+
int retval;
|
94
|
+
|
95
|
+
for (;;) {
|
96
|
+
FD_ZERO(&rfds);
|
97
|
+
FD_SET(0, &rfds);
|
98
|
+
FD_SET(_inotify_fd, &rfds);
|
99
|
+
|
100
|
+
retval = select(_inotify_fd+1, &rfds, NULL, NULL, NULL);
|
101
|
+
|
102
|
+
if (retval == -1) {
|
103
|
+
// perror("select");
|
104
|
+
} else if (retval) {
|
105
|
+
if (FD_ISSET(0, &rfds)) handleStdin();
|
106
|
+
if (FD_ISSET(_inotify_fd, &rfds)) handleInotify();
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
|
112
|
+
int main(int argc, const char *argv[])
|
113
|
+
{
|
114
|
+
_inotify_fd = inotify_init();
|
115
|
+
go();
|
116
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Zeus
|
2
|
+
class LoadTracking
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def features_loaded_by(&block)
|
6
|
+
old_features = all_features()
|
7
|
+
yield
|
8
|
+
new_features = all_features() - old_features
|
9
|
+
return new_features
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_feature(file)
|
13
|
+
path = if File.exist?(File.expand_path(file))
|
14
|
+
File.expand_path(file)
|
15
|
+
else
|
16
|
+
find_in_load_path(file)
|
17
|
+
end
|
18
|
+
add_extra_feature(path) if path
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def all_features
|
24
|
+
untracked = defined?($untracked_features) ? $untracked_features : []
|
25
|
+
$LOADED_FEATURES + untracked
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_extra_feature(path)
|
29
|
+
$untracked_features ||= []
|
30
|
+
$untracked_features << path
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_in_load_path(file)
|
34
|
+
$LOAD_PATH.map { |path| "#{path}/#{file}" }.detect{ |file| File.exist? file }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Kernel
|
41
|
+
|
42
|
+
def load(file, *a)
|
43
|
+
Kernel.load(file, *a)
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
alias_method :__load_without_zeus, :load
|
48
|
+
def load(file, *a)
|
49
|
+
Zeus::LoadTracking.add_feature(file)
|
50
|
+
__load_without_zeus(file, *a)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Zeus
|
4
|
+
module M
|
5
|
+
### Custom wrapper around an array of test methods
|
6
|
+
# In charge of some smart querying, filtering, sorting, etc on the the
|
7
|
+
# test methods
|
8
|
+
class TestCollection
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
# This should act like an array, so forward some common methods over to the
|
12
|
+
# internal collection
|
13
|
+
def_delegators :@collection, :size, :<<, :each, :empty?
|
14
|
+
|
15
|
+
def initialize(collection = nil)
|
16
|
+
@collection = collection || []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Slice out tests that may be within the given line.
|
20
|
+
# Returns a new TestCollection with the results.
|
21
|
+
def within(line)
|
22
|
+
# Into a new collection, filter only the tests that...
|
23
|
+
self.class.new(select do |test|
|
24
|
+
# are within the given boundary for this method
|
25
|
+
# or include everything if the line given is nil (no line)
|
26
|
+
line.nil? || (test.start_line..test.end_line).include?(line)
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used to line up method names in `#sprintf` when `m` aborts
|
31
|
+
def column_size
|
32
|
+
# Boil down the collection of test methods to the name of the method's
|
33
|
+
# size, then find the largest one
|
34
|
+
@column_size ||= map { |test| test.name.to_s.size }.max
|
35
|
+
end
|
36
|
+
|
37
|
+
# Be considerate when printing out tests and pre-sort them by line number
|
38
|
+
def by_line_number(&block)
|
39
|
+
# On each member of the collection, sort by line number and yield
|
40
|
+
# the block into the sorted collection
|
41
|
+
sort_by(&:start_line).each(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def contains? test_name
|
45
|
+
@collection.each do |test|
|
46
|
+
return true if test_name.match(test.name)
|
47
|
+
end
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Zeus
|
2
|
+
module M
|
3
|
+
### Simple data structure for what a test method contains.
|
4
|
+
#
|
5
|
+
# Too lazy to make a class for this when it's really just a bag of data
|
6
|
+
# without any behavior.
|
7
|
+
#
|
8
|
+
# Includes the name of this method, what line on the file it begins on,
|
9
|
+
# and where it ends.
|
10
|
+
class TestMethod < Struct.new(:name, :start_line, :end_line)
|
11
|
+
# Set up a new test method for this test suite class
|
12
|
+
def self.create(suite_class, test_method, find_locations = true)
|
13
|
+
# Hopefully it's been defined as an instance method, so we'll need to
|
14
|
+
# look up the ruby Method instance for it
|
15
|
+
method = suite_class.instance_method(test_method)
|
16
|
+
|
17
|
+
if find_locations
|
18
|
+
# Ruby can find the starting line for us, so pull that out of the array
|
19
|
+
start_line = method.source_location.last
|
20
|
+
|
21
|
+
# Ruby can't find the end line however, and I'm too lazy to write
|
22
|
+
# a parser. Instead, `method_source` adds `Method#source` so we can
|
23
|
+
# deduce this ourselves.
|
24
|
+
#
|
25
|
+
# The end line should be the number of line breaks in the method source,
|
26
|
+
# added to the starting line and subtracted by one.
|
27
|
+
end_line = method.source.split("\n").size + start_line - 1
|
28
|
+
end
|
29
|
+
|
30
|
+
# Shove the given attributes into a new databag
|
31
|
+
new(test_method, start_line, end_line)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|