tork 19.4.0 → 19.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/tork CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK 1 2013-11-25 19.4.0
4
+ # TORK 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -13,7 +13,8 @@ tork - Continuous testing tool for Ruby
13
13
 
14
14
  ## DESCRIPTION
15
15
 
16
- This program is a simple command-line user interface for tork-driver(1).
16
+ This program can be thought of as an interactive version of tork-runner(1).
17
+ It functions as a rudimentary command-line user interface to tork-driver(1).
17
18
 
18
19
  First, it applies the given *CONFIG* values, which are either (1) paths to
19
20
  directories that contain configuration files or (2) names of configuration
@@ -111,7 +112,7 @@ This program can be controlled remotely by multiple tork-remote(1) instances.
111
112
 
112
113
  ## SEE ALSO
113
114
 
114
- tork(1), tork-driver(1), tork-master(1)
115
+ tork-runner(1), tork-driver(1), tork-master(1)
115
116
 
116
117
  [factory_girl]: https://github.com/thoughtbot/factory_girl
117
118
  [memory_test_fix]: https://github.com/stepahn/memory_test_fix
data/bin/tork-driver CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK-DRIVER 1 2013-11-25 19.4.0
4
+ # TORK-DRIVER 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -20,7 +20,10 @@ This program can be controlled remotely by multiple tork-remote(1) instances.
20
20
  ### Input
21
21
 
22
22
  This program reads the following commands, which are single-line JSON arrays,
23
- from stdin and performs the actions described respectively.
23
+ from stdin and then performs the associated actions. For lines read from
24
+ stdin that are single-line JSON arrays, it splits each of them into an array
25
+ of words, using the same word-splitting algorithm as sh(1), before processing
26
+ them. For example, the line `a "b c"` is split into the `["a", "b c"]` array.
24
27
 
25
28
  `["run_all_test_files"]`
26
29
  Runs all test files found within and beneath the current working directory.
data/bin/tork-engine CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK-ENGINE 1 2013-11-25 19.4.0
4
+ # TORK-ENGINE 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -20,7 +20,10 @@ This program can be controlled remotely by multiple tork-remote(1) instances.
20
20
  ### Input
21
21
 
22
22
  This program reads the following commands, which are single-line JSON arrays,
23
- from stdin and performs the actions described respectively.
23
+ from stdin and then performs the associated actions. For lines read from
24
+ stdin that are single-line JSON arrays, it splits each of them into an array
25
+ of words, using the same word-splitting algorithm as sh(1), before processing
26
+ them. For example, the line `a "b c"` is split into the `["a", "b c"]` array.
24
27
 
25
28
  `["reabsorb_overhead"]`
26
29
  Stops any test files that are currently running, reabsorbs the test
data/bin/tork-herald CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK-HERALD 1 2013-11-25 19.4.0
4
+ # TORK-HERALD 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -37,7 +37,7 @@ require 'json'
37
37
  STDOUT.sync = true # flush puts() output immediately after writing
38
38
 
39
39
  require 'listen'
40
- Listen.to('.', :relative_paths => true) do |modified, added, removed|
40
+ Listen.to! '.', :relative_paths => true do |modified, added, removed|
41
41
  files = modified + added
42
42
  puts JSON.dump(files) unless files.empty?
43
- end.start
43
+ end
data/bin/tork-master CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK-MASTER 1 2013-11-25 19.4.0
4
+ # TORK-MASTER 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -23,7 +23,10 @@ This program can be controlled remotely by multiple tork-remote(1) instances.
23
23
  ### Input
24
24
 
25
25
  This program reads the following commands, which are single-line JSON arrays,
26
- from stdin and performs the actions described respectively.
26
+ from stdin and then performs the associated actions. For lines read from
27
+ stdin that are single-line JSON arrays, it splits each of them into an array
28
+ of words, using the same word-splitting algorithm as sh(1), before processing
29
+ them. For example, the line `a "b c"` is split into the `["a", "b c"]` array.
27
30
 
28
31
  `["test",` *test_file*`,` *line_numbers*`]`
29
32
  Forks a worker process to run tests that correspond to the given
data/bin/tork-notify CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  =begin =======================================================================
3
3
 
4
- # TORK-NOTIFY 1 2013-11-25 19.4.0
4
+ # TORK-NOTIFY 1 2013-11-30 19.5.0
5
5
 
6
6
  ## NAME
7
7
 
@@ -58,7 +58,8 @@ IO.popen('tork-remote tork-engine', 'r+') do |remote|
58
58
  statistics = File.readlines(log_file).grep(/^\d+ \w+,/).join.
59
59
  gsub(/\e\[\d+(;\d+)?m/, '') # strip ANSI SGR escape codes
60
60
 
61
- Thread.new do # run in background
61
+ # run in background; see http://stackoverflow.com/q/16745840
62
+ Thread.new(icon, title, statistics) do |icon, title, statistics|
62
63
  system 'notify-send', '-i', icon, title, statistics or
63
64
  system 'growlnotify', '-a', 'Xcode', '-m', statistics, title or
64
65
  system 'xmessage', '-timeout', '5', '-title', title, statistics or
data/bin/tork-remote CHANGED
@@ -13,15 +13,10 @@ tork-remote - controls tork(1) programs
13
13
 
14
14
  ## DESCRIPTION
15
15
 
16
- This program sends single-line JSON messages read from its stdin to the given
17
- *PROGRAM* which is already running in the same working directory as this one.
18
-
19
- If lines read from stdin are not single-line JSON messages, then they are
20
- split into an array of words, using the same word-splitting algorithm as
21
- sh(1), before being sent to the *PROGRAM* as a single-line JSON message.
22
-
23
- If the *PROGRAM* sends any messages in response, then they are printed to
24
- stdout if they are valid single-line JSON messages or to stderr otherwise.
16
+ This program reads lines from its stdin and sends them to the given *PROGRAM*,
17
+ which must already be running in the same working directory as this program.
18
+ It also prints lines, received in response, from the given *PROGRAM* either
19
+ to stdout if they are valid single-line JSON arrays or to stderr otherwise.
25
20
 
26
21
  ## OPTIONS
27
22
 
data/bin/tork-runner ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ =begin =======================================================================
3
+
4
+ # TORK-RUNNER 1 2013-11-30 19.5.0
5
+
6
+ ## NAME
7
+
8
+ tork-runner - runs tests once, non-interactively
9
+
10
+ ## SYNOPSIS
11
+
12
+ `tork-runner` [*OPTION*]... [*TEST\_FILE\_GLOB*]...
13
+
14
+ ## DESCRIPTION
15
+
16
+ This program can be thought of as a non-interactive version of tork(1). It
17
+ runs all test files that match the given *TEST\_FILE\_GLOB*s and then exits
18
+ with a nonzero status if any tests failed. If none are given, it runs all
19
+ test files known to `Tork::Driver::TEST_FILE_GLOBBERS` in tork-driver(1).
20
+
21
+ ### Output
22
+
23
+ This program prints the following messages to stdout.
24
+
25
+ `>>` *failed\_test\_log\_file* `<<`
26
+ This message will be followed by the content of *failed\_test\_log\_file*.
27
+
28
+ *T* `tested,` *P* `passed,` *F* `failed`
29
+ *T* test files were tested and *P* of them passed but *F* of them failed.
30
+
31
+ ## OPTIONS
32
+
33
+ `-h`, `--help`
34
+ Show this help manual.
35
+
36
+ ## EXIT STATUS
37
+
38
+ 0
39
+ All test files passed.
40
+
41
+ 1
42
+ One or more test files failed.
43
+
44
+ ## ENVIRONMENT
45
+
46
+ See tork(1).
47
+
48
+ ## SEE ALSO
49
+
50
+ tork(1), tork-driver(1)
51
+
52
+ =end =========================================================================
53
+
54
+ $0 = File.basename(__FILE__) # for easier identification in ps(1) output
55
+
56
+ require 'binman'
57
+ BinMan.help
58
+
59
+ require 'json'
60
+ IO.popen('tork-driver', 'w+') do |driver|
61
+ # tell tork to run the given test files
62
+ # or run known test files if none given
63
+ test_files = Dir[*ARGV]
64
+ command =
65
+ if test_files.empty?
66
+ [:run_all_test_files]
67
+ else
68
+ [:run_test_files, test_files]
69
+ end
70
+ driver.puts JSON.dump(command)
71
+
72
+ # track test runs & exit when finished
73
+ tested, passed, failed = 0, 0, []
74
+ while line = driver.gets
75
+ response = JSON.load(line)
76
+ case response.first.to_sym
77
+ when :test then tested += 1
78
+ when :pass then passed += 1
79
+ when :fail then failed << response[3]
80
+ when :idle then
81
+ puts failed.map {|log| [nil, ">> #{log} <<", File.read(log)] }, nil,
82
+ "#{tested} tested, #{passed} passed, #{failed.count} failed"
83
+ exit failed.empty?
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,66 @@
1
+ module Tork
2
+ # Wrapper for IO.popen() that automatically re-establishes itself
3
+ # whenever the child process terminates extraneously, on its own.
4
+ class Bridge
5
+
6
+ def initialize command
7
+ @command = command
8
+ connect
9
+ end
10
+
11
+ def disconnect
12
+ return unless @guardian.alive?
13
+
14
+ # prevent guardian from reconnecting bridge while we disconnect it
15
+ @guardian.exit
16
+
17
+ # this should be enough to stop programs that use Tork::Server#loop
18
+ # because their IO.select() loop terminates on the closing of STDIN
19
+ @io.close_write
20
+
21
+ # but some programs like tork-herald(1) need to be killed explicitly
22
+ # because they do not follow our convention of exiting on STDIN close
23
+ Process.kill :SIGTERM, @io.pid
24
+ Process.waitpid @io.pid
25
+
26
+ # this will block until the child process has exited so we must kill it
27
+ # explicitly (as above) to ensure that this program does not hang here
28
+ @io.close_read
29
+
30
+ rescue IOError, SystemCallError
31
+ # IOError happens if the child process' pipes are already closed
32
+ # SystemCallError happens if the child process is already dead
33
+ end
34
+
35
+ def reconnect
36
+ disconnect
37
+ connect
38
+ end
39
+
40
+ # Allows this object to be passed directly
41
+ # into IO.select() and Tork::Server#tell().
42
+ def to_io
43
+ @io
44
+ end
45
+
46
+ # Allows this object to be treated as IO.
47
+ def method_missing *args, &block
48
+ @io.__send__ *args, &block
49
+ end
50
+
51
+ private
52
+
53
+ def connect
54
+ @io = IO.popen(@command, 'r+')
55
+
56
+ # automatically reconnect the bridge when the child process terminates
57
+ @guardian = Thread.new do
58
+ Process.waitpid @io.pid
59
+ warn "#{$0}: repairing collapsed bridge: #{@command} #{$?}"
60
+ sleep 1 # avoid spamming the CPU by waiting a bit before reconnecting
61
+ connect # no need to disconnect because child process is already dead
62
+ end
63
+ end
64
+
65
+ end
66
+ end
data/lib/tork/cliapp.rb CHANGED
@@ -24,6 +24,11 @@ class CLIApp < Server
24
24
 
25
25
  protected
26
26
 
27
+ def join client
28
+ super
29
+ help client
30
+ end
31
+
27
32
  def recv client, message
28
33
  case client
29
34
  when @driver
@@ -51,23 +56,26 @@ protected
51
56
  tell @clients, message, false
52
57
  end
53
58
  else
54
- key, *args = message
55
- key &&= key.lstrip[0,1].downcase
56
-
57
- if cmd = COMMANDS[key]
58
- quit if cmd == :quit
59
- call = Array(cmd) + args
60
- tell @clients, "Sending #{call.inspect} command...", false
61
- send @driver, call
59
+ key = message.shift.lstrip[0,1].downcase
60
+ cmd = Array(COMMANDS.fetch(key, [:help, client])) + message
61
+ if respond_to? cmd.first, true
62
+ __send__(*cmd)
62
63
  else
63
- # user typed an invalid command so help them along
64
- COMMANDS.each do |key, cmd|
65
- desc = Array(cmd).join(' with ').to_s.tr('_', ' ')
66
- tell @client, "Type #{key} then ENTER to #{desc}.", false
67
- end
64
+ tell @clients, "Sending #{cmd.inspect} command...", false
65
+ send @driver, cmd
68
66
  end
69
67
  end
70
68
  end
71
69
 
70
+ private
71
+
72
+ def help client
73
+ COMMANDS.each do |key, cmd|
74
+ desc = Array(cmd).join(' with ').to_s.tr('_', ' ')
75
+ tell client, "Type #{key} then ENTER to #{desc}.", false
76
+ end
77
+ tell client, 'Type h then ENTER to see this message.', false
78
+ end
79
+
72
80
  end
73
81
  end
@@ -10,7 +10,7 @@ if defined? ActiveRecord::Base
10
10
  elsif base.respond_to? :connection_pool # rails >= 2.2.1
11
11
  base.connection_pool.spec.config
12
12
  else
13
- warn "#{$0}: config/rails/worker: couldn't read connection information"
13
+ warn "#{$0}: config/rails/worker: could not read connection information"
14
14
  {}
15
15
  end
16
16
 
data/lib/tork/engine.rb CHANGED
@@ -17,15 +17,14 @@ class Engine < Server
17
17
  end
18
18
 
19
19
  def loop
20
- create_master
20
+ @master = popen('tork-master')
21
21
  super
22
22
  ensure
23
- destroy_master
23
+ pclose @master
24
24
  end
25
25
 
26
26
  def reabsorb_overhead
27
- destroy_master
28
- create_master
27
+ @master.reconnect
29
28
 
30
29
  # re-dispatch the previously dispatched files to the new master
31
30
  previous = @queued_test_files.to_a
@@ -122,13 +121,5 @@ private
122
121
  map {|change| change.position + 1 }.uniq
123
122
  end
124
123
 
125
- def create_master
126
- @master = popen('tork-master')
127
- end
128
-
129
- def destroy_master
130
- pclose @master
131
- end
132
-
133
124
  end
134
125
  end
data/lib/tork/server.rb CHANGED
@@ -2,6 +2,7 @@ require 'socket'
2
2
  require 'json'
3
3
  require 'shellwords'
4
4
  require 'set'
5
+ require 'tork/bridge'
5
6
 
6
7
  module Tork
7
8
  class Server
@@ -11,46 +12,42 @@ class Server
11
12
  end
12
13
 
13
14
  def initialize
15
+ begin
16
+ @welcome = UNIXServer.open(address = Server.address)
17
+ # UNIX domain socket files are not automatically deleted on close
18
+ at_exit { File.delete address if File.socket? address }
19
+ rescue Errno::EADDRINUSE
20
+ # another instance of this program is already running in the same
21
+ # directory so become a remote control for it rather than exiting
22
+ warn "#{$0}: remotely controlling existing instance..."
23
+ exec 'tork-remote', $0
24
+ end
25
+
14
26
  # only JSON messages are supposed to be emitted on STDOUT
15
27
  # so make puts() in the user code write to STDERR instead
16
28
  @stdout = STDOUT.dup
17
29
  STDOUT.reopen STDERR
18
30
 
19
- @clients = Set.new.add(STDIN)
20
- @servers = Set.new
21
- @address = Server.address
31
+ @servers = Set.new.add(@welcome)
32
+ @clients = Set.new; join STDIN # parent process connected on STDIN
22
33
  end
23
34
 
24
35
  def loop
25
- begin
26
- server = UNIXServer.open(@address)
27
- rescue SystemCallError => error
28
- warn "#{$0}: #{error}; retrying in #{timeout = 1 + rand(10)} seconds..."
29
- sleep timeout
30
- retry
31
- end
32
-
33
36
  catch :quit do
34
- @servers.add server
35
37
  while @clients.include? STDIN
36
38
  IO.select((@servers + @clients).to_a).first.each do |stream|
37
- @client = stream
38
-
39
- if stream == server
40
- @clients.add stream.accept
39
+ if stream == @welcome
40
+ join stream.accept
41
41
 
42
42
  elsif (stream.eof? rescue true)
43
- @clients.delete stream
43
+ part stream
44
44
 
45
- elsif @command = hear(stream, stream.gets)
45
+ elsif @command = hear(stream, stream.gets) and not @command.empty?
46
46
  recv stream, @command
47
47
  end
48
48
  end
49
49
  end
50
50
  end
51
- ensure
52
- # UNIX domain socket files are not deleted automatically upon closing
53
- File.delete @address if File.socket? @address
54
51
  end
55
52
 
56
53
  def quit
@@ -59,6 +56,14 @@ class Server
59
56
 
60
57
  protected
61
58
 
59
+ def join client
60
+ @clients.add client
61
+ end
62
+
63
+ def part client
64
+ @clients.delete client
65
+ end
66
+
62
67
  # Returns nil if the message received was not meant for processing.
63
68
  def hear sender, message
64
69
  JSON.load message
@@ -73,7 +78,9 @@ protected
73
78
  end
74
79
  end
75
80
 
81
+ # Sets the @client variable to the client we are currently serving.
76
82
  def recv client, command
83
+ @client = client
77
84
  __send__(*command)
78
85
  rescue => error
79
86
  tell client, error
@@ -93,7 +100,7 @@ protected
93
100
  end
94
101
 
95
102
  targets =
96
- if one_or_more_clients.kind_of? IO
103
+ if one_or_more_clients.respond_to? :to_io
97
104
  [one_or_more_clients]
98
105
  else
99
106
  Array(one_or_more_clients)
@@ -113,26 +120,13 @@ protected
113
120
  end
114
121
 
115
122
  def popen command
116
- child = IO.popen(command, 'r+')
123
+ child = Bridge.new(command)
117
124
  @servers.add child
118
125
  child
119
126
  end
120
127
 
121
128
  def pclose child
122
- return unless @servers.delete? child
123
-
124
- # this should be enough to stop programs that use Tork::Server#loop
125
- # because their IO.select() loop terminates on the closing of STDIN
126
- child.close_write
127
-
128
- # but some programs like tork-herald(1) need to be killed explicitly
129
- # because they do not follow our convention of exiting on STDIN close
130
- Process.kill :SIGTERM, child.pid
131
- Process.waitpid child.pid
132
-
133
- # this will block until the child process has exited so we must kill it
134
- # explicitly (as above) to ensure that this program does not hang here
135
- child.close_read
129
+ child.disconnect if @servers.delete? child
136
130
  end
137
131
 
138
132
  end