blade_runner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +1 -0
- data/Rakefile +2 -0
- data/assets/index.coffee +38 -0
- data/assets/index.html.erb +29 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/blade_runner.gemspec +34 -0
- data/exe/blade +4 -0
- data/lib/blade_runner.rb +166 -0
- data/lib/blade_runner/assets.rb +73 -0
- data/lib/blade_runner/combined_test_results.rb +42 -0
- data/lib/blade_runner/component.rb +5 -0
- data/lib/blade_runner/interface/ci.rb +51 -0
- data/lib/blade_runner/interface/console.rb +125 -0
- data/lib/blade_runner/interface/console_tab.rb +173 -0
- data/lib/blade_runner/model.rb +31 -0
- data/lib/blade_runner/rack_adapter.rb +81 -0
- data/lib/blade_runner/server.rb +29 -0
- data/lib/blade_runner/session.rb +22 -0
- data/lib/blade_runner/test_results.rb +91 -0
- data/lib/blade_runner/version.rb +3 -0
- metadata +223 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dfcd2226f3c4c49763734dd9473ebb0477367713
|
4
|
+
data.tar.gz: 787fd069485edda1dd8948d97c66afbfb72aacf6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4c3609c082d83d0486f4578f70c5ecd6f8a2fc5ba84245d8a05545e9f1e71e8899f652ede77f2fd65c6d0a375ba322367fc7fc1364138b040ed1c9bf4cd49087
|
7
|
+
data.tar.gz: 5d14dcaa121b74e319ebabba9d1162ae367944163b1f5fb070b2afe08d9807d77edaa42048fbd685b2419314569753bc0ad6ae21875275df5ad133965d41d5f3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Javan Makhmali
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Blade Runner: A [Sprockets](https://github.com/rails/sprockets)-friendly JavaScript test runner
|
data/Rakefile
ADDED
data/assets/index.coffee
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#= require faye-browser
|
2
|
+
|
3
|
+
websocketURL = document.querySelector("meta[name=websocket]").getAttribute("content")
|
4
|
+
@client = new Faye.Client websocketURL
|
5
|
+
session_id = document.cookie.match(/blade_runner_session=(\w+)/)?[1]
|
6
|
+
|
7
|
+
publish = ({channel, event, data}) ->
|
8
|
+
if session_id?
|
9
|
+
data = extend(copy(data), {event, session_id})
|
10
|
+
client.publish(channel, data)
|
11
|
+
|
12
|
+
copy = (object) ->
|
13
|
+
results = {}
|
14
|
+
results[key] = value for key, value of object
|
15
|
+
results
|
16
|
+
|
17
|
+
extend = (object, attributes) ->
|
18
|
+
object[key] = value for key, value of attributes
|
19
|
+
object
|
20
|
+
|
21
|
+
if session_id?
|
22
|
+
client.subscribe "/assets", (data) ->
|
23
|
+
location.reload() if data.changed
|
24
|
+
|
25
|
+
setInterval ->
|
26
|
+
publish(channel: "/browsers", event: "ping")
|
27
|
+
, 1000
|
28
|
+
|
29
|
+
@BladeRunner =
|
30
|
+
suiteBegin: ({total}) ->
|
31
|
+
publish("/tests", event: "begin", data: {total})
|
32
|
+
|
33
|
+
testResult: ({name, pass, message}) ->
|
34
|
+
result = pass
|
35
|
+
publish(channel: "/tests", event: "result", data: {result, name, message})
|
36
|
+
|
37
|
+
suiteEnd: (details) ->
|
38
|
+
publish(channel: "/tests", event: "end", data: details)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<title>Blade Runner</title>
|
6
|
+
<meta name="websocket" content="blade_runner/websocket">
|
7
|
+
<script src="blade_runner.js"></script>
|
8
|
+
|
9
|
+
<%= environment(:user)["head.html"] %>
|
10
|
+
|
11
|
+
<% if environment(:adapter)["index.css"] %>
|
12
|
+
<link rel="stylesheet" type="text/css" href="blade_runner/adapter.css">
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<% if environment(:adapter)["index.js"] %>
|
16
|
+
<script src="blade_runner/adapter.js"
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<%= environment(:adapter)["head.html"] %>
|
20
|
+
|
21
|
+
<% logical_paths(:js).each do |path| %>
|
22
|
+
<script src="<%= path %>"></script>
|
23
|
+
<% end %>
|
24
|
+
</head>
|
25
|
+
<body>
|
26
|
+
<%= environment(:user)["body.html"] %>
|
27
|
+
<%= environment(:adapter)["body.html"] %>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "blade_runner"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'blade_runner/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "blade_runner"
|
8
|
+
spec.version = BladeRunner::VERSION
|
9
|
+
spec.authors = ["Javan Makhmali"]
|
10
|
+
spec.email = ["javan@javan.us"]
|
11
|
+
|
12
|
+
spec.summary = %q{Blade Runner}
|
13
|
+
spec.description = %q{A Sprockets-friendly JavaScript test runner}
|
14
|
+
spec.homepage = "https://github.com/javan/blade_runner"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
|
25
|
+
spec.add_dependency "activesupport", ">= 3.0.0"
|
26
|
+
spec.add_dependency "coffee-script", "~> 2.4.0"
|
27
|
+
spec.add_dependency "coffee-script-source", "~> 1.9.0"
|
28
|
+
spec.add_dependency "curses", "~> 1.0.0"
|
29
|
+
spec.add_dependency "eventmachine", "~> 1.0.0"
|
30
|
+
spec.add_dependency "faye", "~> 1.1.1"
|
31
|
+
spec.add_dependency "sprockets", "~> 3.2.0"
|
32
|
+
spec.add_dependency "thin", "~> 1.6.0"
|
33
|
+
spec.add_dependency "useragent", "~> 0.13.0"
|
34
|
+
end
|
data/exe/blade
ADDED
data/lib/blade_runner.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require "blade_runner/version"
|
2
|
+
|
3
|
+
require "eventmachine"
|
4
|
+
require "faye"
|
5
|
+
require "pathname"
|
6
|
+
require "ostruct"
|
7
|
+
require "yaml"
|
8
|
+
|
9
|
+
require "active_support/core_ext/hash"
|
10
|
+
|
11
|
+
module BladeRunner
|
12
|
+
extend self
|
13
|
+
|
14
|
+
@components = []
|
15
|
+
|
16
|
+
def register_component(component)
|
17
|
+
@components << component
|
18
|
+
end
|
19
|
+
|
20
|
+
require "blade_runner/component"
|
21
|
+
require "blade_runner/server"
|
22
|
+
|
23
|
+
autoload :Model, "blade_runner/model"
|
24
|
+
autoload :Assets, "blade_runner/assets"
|
25
|
+
autoload :RackAdapter, "blade_runner/rack_adapter"
|
26
|
+
autoload :Session, "blade_runner/session"
|
27
|
+
autoload :TestResults, "blade_runner/test_results"
|
28
|
+
autoload :CombinedTestResults, "blade_runner/combined_test_results"
|
29
|
+
autoload :Console, "blade_runner/interface/console"
|
30
|
+
autoload :CI, "blade_runner/interface/ci"
|
31
|
+
|
32
|
+
extend Forwardable
|
33
|
+
def_delegators "Server.client", :subscribe, :publish
|
34
|
+
|
35
|
+
ALLOWED_MODES = [:console, :ci]
|
36
|
+
|
37
|
+
DEFAULT_MODE = ALLOWED_MODES.first
|
38
|
+
DEFAULT_PORT = 9876
|
39
|
+
|
40
|
+
attr_reader :config, :plugins
|
41
|
+
|
42
|
+
@running = false
|
43
|
+
@initialized = false
|
44
|
+
|
45
|
+
def start(options = {})
|
46
|
+
return if running?
|
47
|
+
initialize!(options)
|
48
|
+
handle_exit
|
49
|
+
interface
|
50
|
+
|
51
|
+
EM.run do
|
52
|
+
@components.each { |c| c.start if c.respond_to?(:start) }
|
53
|
+
@running = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop
|
58
|
+
return if @stopping
|
59
|
+
@stopping = true
|
60
|
+
@components.each { |c| c.stop if c.respond_to?(:stop) }
|
61
|
+
EM.stop if EM.reactor_running?
|
62
|
+
@running = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def running?
|
66
|
+
@running
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize!(options = {})
|
70
|
+
return if initialized?
|
71
|
+
@options = options.with_indifferent_access
|
72
|
+
load_config_file!
|
73
|
+
read_arguments!
|
74
|
+
setup_config!
|
75
|
+
setup_adapter!
|
76
|
+
setup_plugins!
|
77
|
+
@initialized = true
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialized?
|
81
|
+
@initialized
|
82
|
+
end
|
83
|
+
|
84
|
+
def url(path = "")
|
85
|
+
"http://localhost:#{config.port}#{path}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def interface
|
89
|
+
@interface ||=
|
90
|
+
case config.mode
|
91
|
+
when :ci then CI
|
92
|
+
when :console then Console
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def root_path
|
97
|
+
Pathname.new(File.dirname(__FILE__)).join("../")
|
98
|
+
end
|
99
|
+
|
100
|
+
def tmp_path
|
101
|
+
Pathname.new(".").join("tmp/blade_runner")
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def handle_exit
|
106
|
+
%w( INT ).each do |signal|
|
107
|
+
trap(signal) { stop }
|
108
|
+
end
|
109
|
+
|
110
|
+
at_exit do
|
111
|
+
stop
|
112
|
+
exit $!.status if $!.is_a?(SystemExit)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def load_config_file!
|
117
|
+
filename = ".blade.yml"
|
118
|
+
if File.exists?(filename)
|
119
|
+
@options.reverse_merge!(YAML.load_file(filename))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_arguments!
|
124
|
+
if mode = ARGV[0]
|
125
|
+
@options[:mode] = mode.to_sym
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def setup_config!
|
130
|
+
ignore_options = ALLOWED_MODES + [:plugins]
|
131
|
+
|
132
|
+
options = @options.except(ignore_options)
|
133
|
+
options[:mode] = DEFAULT_MODE unless ALLOWED_MODES.include?(options[:mode])
|
134
|
+
|
135
|
+
if options_for_mode = @options[options[:mode]]
|
136
|
+
options.merge! options_for_mode.except(ignore_options)
|
137
|
+
end
|
138
|
+
|
139
|
+
options[:port] ||= DEFAULT_PORT
|
140
|
+
options[:load_paths] = Array(options[:load_paths])
|
141
|
+
options[:logical_paths] = Array(options[:logical_paths])
|
142
|
+
|
143
|
+
@config = OpenStruct.new(options)
|
144
|
+
end
|
145
|
+
|
146
|
+
def setup_adapter!
|
147
|
+
require "blade_runner/#{config.framework}_adapter"
|
148
|
+
end
|
149
|
+
|
150
|
+
def setup_plugins!
|
151
|
+
@plugins = OpenStruct.new
|
152
|
+
|
153
|
+
plugin_config = @options[:plugins] || {}
|
154
|
+
|
155
|
+
if plugins_for_mode = (@options[config.mode] || {})[:plugins]
|
156
|
+
plugin_config.merge! plugins_for_mode
|
157
|
+
end
|
158
|
+
|
159
|
+
plugin_config.each do |name, plugin_config|
|
160
|
+
plugins[name] = OpenStruct.new(config: OpenStruct.new(plugin_config))
|
161
|
+
require "blade_runner/#{name}_plugin"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
BR = BladeRunner
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "sprockets"
|
2
|
+
|
3
|
+
module BladeRunner::Assets
|
4
|
+
extend self
|
5
|
+
|
6
|
+
@environments = {}
|
7
|
+
|
8
|
+
def environment(name = :blade_runner)
|
9
|
+
@environments[name] ||= Sprockets::Environment.new do |env|
|
10
|
+
env.cache = Sprockets::Cache::FileStore.new(BR.tmp_path.join(name.to_s))
|
11
|
+
|
12
|
+
send("#{name}_load_paths").each do |path|
|
13
|
+
env.append_path(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
env.context_class.class_eval do
|
17
|
+
extend Forwardable
|
18
|
+
def_delegators "BladeRunner::Assets", :environment, :logical_paths
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def logical_paths(type = nil)
|
24
|
+
paths = BR.config.logical_paths
|
25
|
+
paths.select! { |path| File.extname(path) == ".#{type}" } if type
|
26
|
+
paths
|
27
|
+
end
|
28
|
+
|
29
|
+
def blade_runner_load_paths
|
30
|
+
[ BR.root_path.join("assets"), gem_pathname("faye").join("lib") ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def user_load_paths
|
34
|
+
BR.config.load_paths.map { |a| Pathname.new(a) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def adapter_load_paths
|
38
|
+
gem_name = "blade_runner-#{BR.config.framework}_adapter"
|
39
|
+
[ gem_pathname(gem_name).join("assets") ]
|
40
|
+
end
|
41
|
+
|
42
|
+
def watch_logical_paths
|
43
|
+
@mtimes = get_mtimes
|
44
|
+
|
45
|
+
EM.add_periodic_timer(1) do
|
46
|
+
mtimes = get_mtimes
|
47
|
+
unless mtimes == @mtimes
|
48
|
+
@mtimes = mtimes
|
49
|
+
BR.publish("/assets", changed: @mtimes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def get_mtimes
|
56
|
+
{}.tap do |mtimes|
|
57
|
+
BR.config.logical_paths.each do |path|
|
58
|
+
mtimes[path] = get_mtime(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_mtime(logical_path)
|
64
|
+
environment_for(:user)[logical_path].mtime
|
65
|
+
rescue Exception => e
|
66
|
+
e.to_s
|
67
|
+
end
|
68
|
+
|
69
|
+
def gem_pathname(gem_name)
|
70
|
+
gemspec = Gem::Specification.find_by_name(gem_name)
|
71
|
+
Pathname.new(gemspec.gem_dir)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class BladeRunner::CombinedTestResults
|
2
|
+
attr_reader :sessions, :all_test_results
|
3
|
+
|
4
|
+
def initialize(sessions)
|
5
|
+
@sessions = sessions
|
6
|
+
@all_test_results = sessions.map(&:test_results)
|
7
|
+
end
|
8
|
+
|
9
|
+
def total
|
10
|
+
sum(totals)
|
11
|
+
end
|
12
|
+
|
13
|
+
def lines(type = :results)
|
14
|
+
sessions.flat_map do |session|
|
15
|
+
session.test_results.send(type).map do |line|
|
16
|
+
line.sub(/ok/, "ok [#{session}]")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
lines = ["1..#{total}"] + lines(:failures) + lines(:passes)
|
23
|
+
lines.join("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def failed?
|
27
|
+
statuses.include?("failed")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def sum(values)
|
32
|
+
values.inject(0) { |sum, total| sum + total }
|
33
|
+
end
|
34
|
+
|
35
|
+
def totals
|
36
|
+
all_test_results.map(&:total).compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def statuses
|
40
|
+
all_test_results.map(&:status)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module BladeRunner::CI
|
2
|
+
extend self
|
3
|
+
include BladeRunner::Component
|
4
|
+
|
5
|
+
def start
|
6
|
+
@completed_sessions = 0
|
7
|
+
|
8
|
+
log "# Running"
|
9
|
+
BR.subscribe("/results") do |details|
|
10
|
+
process_result(details)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def process_result(details)
|
16
|
+
if details.has_key?("pass")
|
17
|
+
log details["pass"] ? "." : "F"
|
18
|
+
end
|
19
|
+
|
20
|
+
if details["completed"]
|
21
|
+
process_completion
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_completion
|
26
|
+
@completed_sessions += 1
|
27
|
+
|
28
|
+
if done?
|
29
|
+
log "\n"
|
30
|
+
display_results_and_exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def done?
|
35
|
+
@completed_sessions == (BR.config.expected_sessions || 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def display_results_and_exit
|
39
|
+
results = BR::Session.combined_test_results
|
40
|
+
display results
|
41
|
+
exit results.failed? ? 1 : 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def log(message)
|
45
|
+
STDERR.print message.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def display(message)
|
49
|
+
STDOUT.puts message.to_s
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "curses"
|
2
|
+
|
3
|
+
module BladeRunner::Console
|
4
|
+
extend self
|
5
|
+
include BladeRunner::Component
|
6
|
+
|
7
|
+
autoload :Tab, "blade_runner/interface/console_tab"
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators "BladeRunner::Console", :create_window
|
11
|
+
|
12
|
+
COLOR_NAMES = %w( white yellow green red )
|
13
|
+
PADDING = 1
|
14
|
+
|
15
|
+
def colors
|
16
|
+
@colors ||= OpenStruct.new.tap do |colors|
|
17
|
+
COLOR_NAMES.each do |name|
|
18
|
+
const = Curses.const_get("COLOR_#{name.upcase}")
|
19
|
+
Curses.init_pair(const, const, Curses::COLOR_BLACK)
|
20
|
+
colors[name] = Curses.color_pair(const)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_window(options = {})
|
26
|
+
height = options[:height] || 0
|
27
|
+
width = options[:width] || 0
|
28
|
+
top = options[:top] || 0
|
29
|
+
left = options[:left] || PADDING
|
30
|
+
parent = options[:parent] || Curses.stdscr
|
31
|
+
|
32
|
+
parent.subwin(height, width, top, left)
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
run
|
37
|
+
BR::Assets.watch_logical_paths
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
Curses.close_screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
start_screen
|
46
|
+
init_windows
|
47
|
+
handle_keys
|
48
|
+
handle_stale_tabs
|
49
|
+
|
50
|
+
BR.subscribe("/results") do |details|
|
51
|
+
session = BR::Session.find(details["session_id"])
|
52
|
+
|
53
|
+
if tab = Tab.find(session.id)
|
54
|
+
if details["line"] && tab.active?
|
55
|
+
Tab.content_window.addstr(details["line"] + "\n")
|
56
|
+
Tab.content_window.noutrefresh
|
57
|
+
end
|
58
|
+
tab.draw
|
59
|
+
else
|
60
|
+
tab = Tab.create(id: session.id)
|
61
|
+
tab.activate if Tab.size == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
Curses.doupdate
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def start_screen
|
70
|
+
Curses.init_screen
|
71
|
+
Curses.start_color
|
72
|
+
Curses.noecho
|
73
|
+
Curses.curs_set(0)
|
74
|
+
Curses.stdscr.keypad(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
def init_windows
|
78
|
+
header_window = create_window(height: 3)
|
79
|
+
header_window.attron(Curses::A_BOLD)
|
80
|
+
header_window.addstr "BLADE RUNNER [press 'q' to quit]\n"
|
81
|
+
header_window.attroff(Curses::A_BOLD)
|
82
|
+
header_window.addstr "Open #{BR.url} to start"
|
83
|
+
header_window.noutrefresh
|
84
|
+
|
85
|
+
Tab.install(top: header_window.maxy)
|
86
|
+
|
87
|
+
Curses.doupdate
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_keys
|
91
|
+
EM.defer do
|
92
|
+
while ch = Curses.getch
|
93
|
+
case ch
|
94
|
+
when Curses::KEY_LEFT
|
95
|
+
Tab.active.activate_previous
|
96
|
+
Curses.doupdate
|
97
|
+
when Curses::KEY_RIGHT
|
98
|
+
Tab.active.activate_next
|
99
|
+
Curses.doupdate
|
100
|
+
when "q"
|
101
|
+
BR.stop
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_stale_tabs
|
108
|
+
BR.subscribe("/browsers") do |details|
|
109
|
+
if details["message"] = "ping"
|
110
|
+
if tab = Tab.find(details["session_id"])
|
111
|
+
tab.last_ping_at = Time.now
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
EM.add_periodic_timer(1) do
|
117
|
+
Tab.stale.each { |t| remove_tab(t) }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def remove_tab(tab)
|
122
|
+
Tab.remove(tab.id)
|
123
|
+
Curses.doupdate
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
class BladeRunner::Console::Tab < BladeRunner::Model
|
2
|
+
extend Forwardable
|
3
|
+
def_delegators "BladeRunner::Console", :colors, :create_window
|
4
|
+
|
5
|
+
class << self
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators "BladeRunner::Console", :create_window
|
8
|
+
|
9
|
+
attr_reader :window, :status_window, :content_window
|
10
|
+
|
11
|
+
def install(options = {})
|
12
|
+
top = options[:top]
|
13
|
+
@window = create_window(top: top, height: 3)
|
14
|
+
|
15
|
+
top = @window.begy + @window.maxy + 1
|
16
|
+
@status_window = create_window(top: top, height: 1)
|
17
|
+
|
18
|
+
top = @status_window.begy + @status_window.maxy + 1
|
19
|
+
@content_window = create_window(top: top)
|
20
|
+
@content_window.scrollok(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def draw
|
24
|
+
window.clear
|
25
|
+
window.noutrefresh
|
26
|
+
all.each(&:draw)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove(id)
|
30
|
+
tab = find(id)
|
31
|
+
tab.deactivate
|
32
|
+
tab.window.close
|
33
|
+
super
|
34
|
+
draw
|
35
|
+
end
|
36
|
+
|
37
|
+
def active
|
38
|
+
all.detect(&:active?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def stale
|
42
|
+
threshold = Time.now - 2
|
43
|
+
all.select { |t| t.last_ping_at && t.last_ping_at < threshold }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def tabs
|
48
|
+
self.class
|
49
|
+
end
|
50
|
+
|
51
|
+
def height
|
52
|
+
3
|
53
|
+
end
|
54
|
+
|
55
|
+
def width
|
56
|
+
5
|
57
|
+
end
|
58
|
+
|
59
|
+
def top
|
60
|
+
tabs.window.begy
|
61
|
+
end
|
62
|
+
|
63
|
+
def left
|
64
|
+
tabs.window.begx + index * width
|
65
|
+
end
|
66
|
+
|
67
|
+
def window
|
68
|
+
@window ||= create_window(height: height, width: width, top: top, left: left)
|
69
|
+
end
|
70
|
+
|
71
|
+
def draw
|
72
|
+
window.clear
|
73
|
+
active? ? draw_active : draw_inactive
|
74
|
+
window.noutrefresh
|
75
|
+
end
|
76
|
+
|
77
|
+
def draw_active
|
78
|
+
window.addstr "╔═══╗"
|
79
|
+
window.addstr "║ "
|
80
|
+
window.attron(color)
|
81
|
+
window.addstr(dot)
|
82
|
+
window.attroff(color)
|
83
|
+
window.addstr(" ║")
|
84
|
+
window.addstr "╝ ╚"
|
85
|
+
end
|
86
|
+
|
87
|
+
def draw_inactive
|
88
|
+
window.addstr "\n"
|
89
|
+
window.attron(color)
|
90
|
+
window.addstr(" #{dot}\n")
|
91
|
+
window.attroff(color)
|
92
|
+
window.addstr "═════"
|
93
|
+
end
|
94
|
+
|
95
|
+
def dot
|
96
|
+
status == "pending" ? "○" : "●"
|
97
|
+
end
|
98
|
+
|
99
|
+
def index
|
100
|
+
tabs.all.index(self)
|
101
|
+
end
|
102
|
+
|
103
|
+
def session
|
104
|
+
BR::Session.find(id)
|
105
|
+
end
|
106
|
+
|
107
|
+
def status
|
108
|
+
session.test_results.status
|
109
|
+
end
|
110
|
+
|
111
|
+
def active?
|
112
|
+
active
|
113
|
+
end
|
114
|
+
|
115
|
+
def activate
|
116
|
+
return if active?
|
117
|
+
|
118
|
+
if tab = tabs.active
|
119
|
+
tab.deactivate
|
120
|
+
end
|
121
|
+
|
122
|
+
self.active = true
|
123
|
+
draw
|
124
|
+
|
125
|
+
tabs.status_window.addstr(session.to_s)
|
126
|
+
tabs.status_window.noutrefresh
|
127
|
+
|
128
|
+
tabs.content_window.addstr(session.test_results.to_s)
|
129
|
+
tabs.content_window.noutrefresh
|
130
|
+
end
|
131
|
+
|
132
|
+
def deactivate
|
133
|
+
return unless active?
|
134
|
+
|
135
|
+
self.active = false
|
136
|
+
draw
|
137
|
+
|
138
|
+
tabs.status_window.clear
|
139
|
+
tabs.status_window.noutrefresh
|
140
|
+
|
141
|
+
tabs.content_window.clear
|
142
|
+
tabs.content_window.noutrefresh
|
143
|
+
end
|
144
|
+
|
145
|
+
def activate_next
|
146
|
+
tabs = tabs.all
|
147
|
+
|
148
|
+
if tabs.last == self
|
149
|
+
tabs.first.activate
|
150
|
+
elsif tab = tabs[index + 1]
|
151
|
+
tab.activate
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def activate_previous
|
156
|
+
tabs = tabs.all
|
157
|
+
|
158
|
+
if tabs.first == self
|
159
|
+
tabs.last.activate
|
160
|
+
elsif tab = tabs[index - 1]
|
161
|
+
tab.activate
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def color
|
166
|
+
case status
|
167
|
+
when "running" then colors.yellow
|
168
|
+
when "finished" then colors.green
|
169
|
+
when /fail/ then colors.red
|
170
|
+
else colors.white
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
class BladeRunner::Model < OpenStruct
|
4
|
+
class << self
|
5
|
+
def models
|
6
|
+
@models ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def create(attributes)
|
10
|
+
attributes[:id] ||= SecureRandom.hex(4)
|
11
|
+
model = new(attributes)
|
12
|
+
models[model.id] = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(id)
|
16
|
+
models[id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove(id)
|
20
|
+
models.delete(id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def all
|
24
|
+
models.values
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
models.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class BladeRunner::RackAdapter
|
2
|
+
extend Forwardable
|
3
|
+
def_delegators "BladeRunner::Assets", :environment
|
4
|
+
|
5
|
+
PATH = "/blade_runner"
|
6
|
+
ADAPTER_PATH = PATH + "/adapter"
|
7
|
+
WEBSOCKET_PATH = PATH + "/websocket"
|
8
|
+
|
9
|
+
def initialize(app = nil, options = {})
|
10
|
+
BR.initialize!
|
11
|
+
|
12
|
+
if app.is_a?(Hash)
|
13
|
+
@options = app
|
14
|
+
else
|
15
|
+
@app, @options = app, options
|
16
|
+
end
|
17
|
+
|
18
|
+
@mount_path = @options[:mount]
|
19
|
+
@mount_path_pattern = /^#{@mount_path}/
|
20
|
+
|
21
|
+
@blade_runner_path_pattern = /^#{PATH}/
|
22
|
+
@adapter_path_pattern = /^#{ADAPTER_PATH}/
|
23
|
+
@websocket_path_pattern = /^#{WEBSOCKET_PATH}/
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
unless @mount_path.nil?
|
28
|
+
if env["PATH_INFO"] =~ @mount_path_pattern
|
29
|
+
env["PATH_INFO"].sub!(@mount_path_pattern, "")
|
30
|
+
elsif @app
|
31
|
+
return @app.call(env)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
case env["PATH_INFO"]
|
36
|
+
when ""
|
37
|
+
add_forward_slash(env)
|
38
|
+
when "/"
|
39
|
+
env["PATH_INFO"] = "index.html"
|
40
|
+
response = environment(:blade_runner).call(env)
|
41
|
+
response_with_session(response, env)
|
42
|
+
when @websocket_path_pattern
|
43
|
+
bayeux.call(env)
|
44
|
+
when @adapter_path_pattern
|
45
|
+
env["PATH_INFO"].sub!(@adapter_path_pattern, "")
|
46
|
+
environment(:adapter).call(env)
|
47
|
+
when @blade_runner_path_pattern
|
48
|
+
env["PATH_INFO"].sub!(@blade_runner_path_pattern, "")
|
49
|
+
environment(:blade_runner).call(env)
|
50
|
+
else
|
51
|
+
environment(:user).call(env)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def bayeux
|
57
|
+
@bayeux ||= Faye::RackAdapter.new(mount: WEBSOCKET_PATH, timeout: 25)
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_forward_slash(env)
|
61
|
+
path = @mount_path || env["REQUEST_PATH"] || env["SCRIPT_NAME"]
|
62
|
+
redirect_to(File.join(path.to_s, "/"))
|
63
|
+
end
|
64
|
+
|
65
|
+
def redirect_to(location, status = 301)
|
66
|
+
[status, { Location: location }, []]
|
67
|
+
end
|
68
|
+
|
69
|
+
def response_with_session(response, env)
|
70
|
+
if BR.running?
|
71
|
+
user_agent = UserAgent.parse(env["HTTP_USER_AGENT"])
|
72
|
+
session = BR::Session.create(user_agent: user_agent)
|
73
|
+
status, headers, body = response
|
74
|
+
response = Rack::Response.new(body, status, headers)
|
75
|
+
response.set_cookie("blade_runner_session", session.id)
|
76
|
+
response.finish
|
77
|
+
else
|
78
|
+
response
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "faye/websocket"
|
2
|
+
require "useragent"
|
3
|
+
|
4
|
+
module BladeRunner::Server
|
5
|
+
extend self
|
6
|
+
include BladeRunner::Component
|
7
|
+
|
8
|
+
WEBSOCKET_PATH = "/blade_runner/websocket"
|
9
|
+
|
10
|
+
def start
|
11
|
+
Faye::WebSocket.load_adapter("thin")
|
12
|
+
Rack::Server.start(app: app, Port: BR.config.port, server: "thin")
|
13
|
+
end
|
14
|
+
|
15
|
+
def websocket_url(path = "")
|
16
|
+
BR.url(WEBSOCKET_PATH + path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def client
|
20
|
+
@client ||= Faye::Client.new(websocket_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def app
|
25
|
+
Rack::Builder.app do
|
26
|
+
run BladeRunner::RackAdapter.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class BladeRunner::Session < BladeRunner::Model
|
2
|
+
class << self
|
3
|
+
def create(attributes)
|
4
|
+
model = super
|
5
|
+
model.test_results = BladeRunner::TestResults.new(model.id)
|
6
|
+
model
|
7
|
+
end
|
8
|
+
|
9
|
+
def combined_test_results
|
10
|
+
BladeRunner::CombinedTestResults.new(all)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@to_s ||= "#{ua.browser} #{ua.version} #{ua.platform}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def ua
|
20
|
+
user_agent
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class BladeRunner::TestResults
|
2
|
+
attr_reader :session_id, :status, :lines, :passes, :failures
|
3
|
+
|
4
|
+
def initialize(session_id)
|
5
|
+
@session_id = session_id
|
6
|
+
reset
|
7
|
+
|
8
|
+
BR.subscribe("/tests") do |details|
|
9
|
+
if details["session_id"] == session_id
|
10
|
+
process_test_result(details)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@lines = []
|
17
|
+
@passes = []
|
18
|
+
@failures = []
|
19
|
+
@completed = false
|
20
|
+
@status = "pending"
|
21
|
+
@total = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_test_result(details)
|
25
|
+
publication = {}
|
26
|
+
|
27
|
+
case details["event"]
|
28
|
+
when "begin"
|
29
|
+
reset
|
30
|
+
@status = "running"
|
31
|
+
@total = details["total"]
|
32
|
+
@lines << publication[:line] = "1..#{@total}"
|
33
|
+
when "result"
|
34
|
+
pass = details["result"]
|
35
|
+
args = details.values_at("name", "message")
|
36
|
+
|
37
|
+
if pass
|
38
|
+
line = Pass.new(*args).to_s
|
39
|
+
@passes << line
|
40
|
+
else
|
41
|
+
line = Failure.new(*args).to_s
|
42
|
+
@failures << line
|
43
|
+
@status = "failing"
|
44
|
+
end
|
45
|
+
@lines << line
|
46
|
+
|
47
|
+
publication.merge!(line: line, pass: pass)
|
48
|
+
when "end"
|
49
|
+
@status = failures.any? ? "failed" : "finished"
|
50
|
+
@completed = true
|
51
|
+
end
|
52
|
+
|
53
|
+
publication.merge!(status: status, session_id: session_id, completed: @completed)
|
54
|
+
BR.publish("/results", publication)
|
55
|
+
end
|
56
|
+
|
57
|
+
def total
|
58
|
+
if @total
|
59
|
+
@total
|
60
|
+
elsif @completed
|
61
|
+
passes.size + failures.size
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
lines.join("\n") + "\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
class Pass
|
70
|
+
def initialize(name, message)
|
71
|
+
@name = name
|
72
|
+
@message = message
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s(number = nil)
|
76
|
+
["ok", number, @name, message].compact.join(" ")
|
77
|
+
end
|
78
|
+
|
79
|
+
def message
|
80
|
+
unless @message.nil?
|
81
|
+
"\n" + @message.gsub(/^/, "# ").chomp
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Failure < Pass
|
87
|
+
def to_s(*args)
|
88
|
+
"not #{super}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
metadata
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blade_runner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Javan Makhmali
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coffee-script
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.4.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.4.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coffee-script-source
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.9.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.9.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: curses
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.0.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.0.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: eventmachine
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.0.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.0.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: faye
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.1.1
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.1.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sprockets
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.2.0
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.2.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: thin
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.6.0
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.6.0
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: useragent
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.13.0
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.13.0
|
167
|
+
description: A Sprockets-friendly JavaScript test runner
|
168
|
+
email:
|
169
|
+
- javan@javan.us
|
170
|
+
executables:
|
171
|
+
- blade
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- ".gitignore"
|
176
|
+
- Gemfile
|
177
|
+
- LICENSE.txt
|
178
|
+
- README.md
|
179
|
+
- Rakefile
|
180
|
+
- assets/index.coffee
|
181
|
+
- assets/index.html.erb
|
182
|
+
- bin/console
|
183
|
+
- bin/setup
|
184
|
+
- blade_runner.gemspec
|
185
|
+
- exe/blade
|
186
|
+
- lib/blade_runner.rb
|
187
|
+
- lib/blade_runner/assets.rb
|
188
|
+
- lib/blade_runner/combined_test_results.rb
|
189
|
+
- lib/blade_runner/component.rb
|
190
|
+
- lib/blade_runner/interface/ci.rb
|
191
|
+
- lib/blade_runner/interface/console.rb
|
192
|
+
- lib/blade_runner/interface/console_tab.rb
|
193
|
+
- lib/blade_runner/model.rb
|
194
|
+
- lib/blade_runner/rack_adapter.rb
|
195
|
+
- lib/blade_runner/server.rb
|
196
|
+
- lib/blade_runner/session.rb
|
197
|
+
- lib/blade_runner/test_results.rb
|
198
|
+
- lib/blade_runner/version.rb
|
199
|
+
homepage: https://github.com/javan/blade_runner
|
200
|
+
licenses:
|
201
|
+
- MIT
|
202
|
+
metadata: {}
|
203
|
+
post_install_message:
|
204
|
+
rdoc_options: []
|
205
|
+
require_paths:
|
206
|
+
- lib
|
207
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
requirements: []
|
218
|
+
rubyforge_project:
|
219
|
+
rubygems_version: 2.4.6
|
220
|
+
signing_key:
|
221
|
+
specification_version: 4
|
222
|
+
summary: Blade Runner
|
223
|
+
test_files: []
|