tork 19.4.0 → 19.5.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.
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