hydra 0.24.0 → 6.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +17 -0
  2. data/CONTRIBUTING.md +75 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +14 -0
  5. data/README.md +36 -0
  6. data/RELEASE-POLICY.md +10 -0
  7. data/Rakefile +1 -56
  8. data/hydra.gemspec +29 -124
  9. data/lib/hydra.rb +5 -16
  10. data/lib/hydra/version.rb +3 -0
  11. metadata +180 -108
  12. data/.document +0 -5
  13. data/LICENSE +0 -20
  14. data/README.rdoc +0 -43
  15. data/TODO +0 -18
  16. data/VERSION +0 -1
  17. data/caliper.yml +0 -6
  18. data/hydra-icon-64x64.png +0 -0
  19. data/hydra_gray.png +0 -0
  20. data/lib/hydra/cucumber/formatter.rb +0 -29
  21. data/lib/hydra/cucumber/partial_html.rb +0 -24
  22. data/lib/hydra/hash.rb +0 -16
  23. data/lib/hydra/js/lint.js +0 -5150
  24. data/lib/hydra/listener/abstract.rb +0 -39
  25. data/lib/hydra/listener/cucumber.css +0 -279
  26. data/lib/hydra/listener/cucumber_html_report.rb +0 -148
  27. data/lib/hydra/listener/jquery-min.js +0 -154
  28. data/lib/hydra/listener/minimal_output.rb +0 -24
  29. data/lib/hydra/listener/notifier.rb +0 -17
  30. data/lib/hydra/listener/progress_bar.rb +0 -48
  31. data/lib/hydra/listener/report_generator.rb +0 -33
  32. data/lib/hydra/master.rb +0 -248
  33. data/lib/hydra/message.rb +0 -47
  34. data/lib/hydra/message/master_messages.rb +0 -19
  35. data/lib/hydra/message/runner_messages.rb +0 -46
  36. data/lib/hydra/message/worker_messages.rb +0 -52
  37. data/lib/hydra/messaging_io.rb +0 -49
  38. data/lib/hydra/pipe.rb +0 -61
  39. data/lib/hydra/runner.rb +0 -312
  40. data/lib/hydra/runner_listener/abstract.rb +0 -23
  41. data/lib/hydra/safe_fork.rb +0 -31
  42. data/lib/hydra/spec/autorun_override.rb +0 -3
  43. data/lib/hydra/spec/hydra_formatter.rb +0 -26
  44. data/lib/hydra/ssh.rb +0 -41
  45. data/lib/hydra/stdio.rb +0 -16
  46. data/lib/hydra/sync.rb +0 -99
  47. data/lib/hydra/tasks.rb +0 -375
  48. data/lib/hydra/tmpdir.rb +0 -11
  49. data/lib/hydra/trace.rb +0 -24
  50. data/lib/hydra/worker.rb +0 -170
  51. data/test/fixtures/assert_true.rb +0 -7
  52. data/test/fixtures/config.yml +0 -4
  53. data/test/fixtures/conflicting.rb +0 -10
  54. data/test/fixtures/features/step_definitions.rb +0 -21
  55. data/test/fixtures/features/write_alternate_file.feature +0 -7
  56. data/test/fixtures/features/write_file.feature +0 -7
  57. data/test/fixtures/hello_world.rb +0 -3
  58. data/test/fixtures/hydra_worker_init.rb +0 -2
  59. data/test/fixtures/js_file.js +0 -4
  60. data/test/fixtures/json_data.json +0 -4
  61. data/test/fixtures/many_outputs_to_console.rb +0 -9
  62. data/test/fixtures/master_listeners.rb +0 -10
  63. data/test/fixtures/runner_listeners.rb +0 -23
  64. data/test/fixtures/slow.rb +0 -9
  65. data/test/fixtures/sync_test.rb +0 -8
  66. data/test/fixtures/task_test_config.yml +0 -6
  67. data/test/fixtures/write_file.rb +0 -10
  68. data/test/fixtures/write_file_alternate_spec.rb +0 -10
  69. data/test/fixtures/write_file_spec.rb +0 -9
  70. data/test/fixtures/write_file_with_pending_spec.rb +0 -11
  71. data/test/master_test.rb +0 -383
  72. data/test/message_test.rb +0 -31
  73. data/test/pipe_test.rb +0 -38
  74. data/test/runner_test.rb +0 -196
  75. data/test/ssh_test.rb +0 -25
  76. data/test/sync_test.rb +0 -113
  77. data/test/task_test.rb +0 -21
  78. data/test/test_helper.rb +0 -107
  79. data/test/worker_test.rb +0 -60
@@ -1,31 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
-
3
- class MessageTest < Test::Unit::TestCase
4
- class MyMessage < Hydra::Message
5
- attr_accessor :my_var
6
- def serialize
7
- super(:my_var => @my_var)
8
- end
9
- end
10
-
11
- context "with a message" do
12
- setup do
13
- @m = MyMessage.new(:my_var => 'my value')
14
- end
15
- should "set values" do
16
- assert_equal 'my value', @m.my_var
17
- end
18
- should "serialize" do
19
- assert_equal(
20
- {:class=>MyMessage, :my_var=>"my value"},
21
- eval(@m.serialize)
22
- )
23
- end
24
- should "build from serialization" do
25
- assert_equal(
26
- @m.my_var,
27
- Hydra::Message.build(eval(@m.serialize)).my_var
28
- )
29
- end
30
- end
31
- end
@@ -1,38 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
-
3
- class PipeTest < Test::Unit::TestCase
4
- context "a pipe" do
5
- setup do
6
- @pipe = Hydra::Pipe.new
7
- end
8
- teardown do
9
- @pipe.close
10
- end
11
- should "be able to write messages" do
12
- child = Process.fork do
13
- @pipe.identify_as_child
14
- assert_equal "Test Message", @pipe.gets.text
15
- @pipe.write Hydra::Messages::TestMessage.new(:text => "Message Received")
16
- @pipe.write Hydra::Messages::TestMessage.new(:text => "Second Message")
17
- end
18
- @pipe.identify_as_parent
19
- @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
20
- assert_equal "Message Received", @pipe.gets.text
21
- assert_equal "Second Message", @pipe.gets.text
22
- Process.wait(child) #ensure it quits, so there is nothing to write to
23
- assert_raise IOError do
24
- @pipe.write Hydra::Messages::TestMessage.new(:text => "anyone there?")
25
- end
26
- end
27
- should "not allow writing if unidentified" do
28
- assert_raise IOError do
29
- @pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
30
- end
31
- end
32
- should "not allow reading if unidentified" do
33
- assert_raise IOError do
34
- @pipe.gets
35
- end
36
- end
37
- end
38
- end
@@ -1,196 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
- require File.join(File.dirname(__FILE__), 'fixtures', 'runner_listeners')
3
-
4
- class RunnerTest < Test::Unit::TestCase
5
- context "with a file to test and a destination to verify" do
6
- setup do
7
- sleep(0.2)
8
- FileUtils.rm_f(target_file)
9
- FileUtils.rm_f(alternate_target_file)
10
- end
11
-
12
- teardown do
13
- FileUtils.rm_f(target_file)
14
- FileUtils.rm_f(alternate_target_file)
15
- end
16
-
17
-
18
- should "run a test in the foreground" do
19
- # flip it around to the parent is in the fork, this gives
20
- # us more direct control over the runner and proper test
21
- # coverage output
22
- pipe = Hydra::Pipe.new
23
- parent = Process.fork do
24
- request_a_file_and_verify_completion(pipe, test_file)
25
- end
26
- run_the_runner(pipe)
27
- Process.wait(parent)
28
- end
29
-
30
- # this flips the above test, so that the main process runs a bit of the parent
31
- # code, but only with minimal assertion
32
- should "run a test in the background" do
33
- pipe = Hydra::Pipe.new
34
- child = Process.fork do
35
- run_the_runner(pipe)
36
- end
37
- request_a_file_and_verify_completion(pipe, test_file)
38
- Process.wait(child)
39
- end
40
-
41
- should "run a js lint file and find errors" do
42
- runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
43
- results = runner.run_file(javascript_file)
44
- assert results =~ /Missing semicolon/, results
45
- end
46
-
47
- should "run a json data file and find errors" do
48
- runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
49
- results = runner.run_file(json_file)
50
- assert results =~ /trailing comma/, results
51
- end
52
-
53
- should "run two rspec tests" do
54
- runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
55
- runner.run_file(rspec_file)
56
- assert File.exists?(target_file)
57
- assert_equal "HYDRA", File.read(target_file)
58
-
59
- FileUtils.rm_f(target_file)
60
-
61
- runner.run_file(alternate_rspec_file)
62
- assert File.exists?(alternate_target_file)
63
- assert_equal "HYDRA", File.read(alternate_target_file)
64
- assert !File.exists?(target_file), "Tests are double running!"
65
- end
66
-
67
- should "run rspec tests with pending examples" do
68
- runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
69
- assert File.exists?(rspec_file_with_pending)
70
-
71
- runner.run_file(rspec_file_with_pending)
72
-
73
- assert File.exists?(target_file)
74
- assert_equal "HYDRA", File.read(target_file)
75
-
76
- FileUtils.rm_f(target_file)
77
- end
78
-
79
- should "run two cucumber tests" do
80
- # because of all the crap cucumber pulls in
81
- # we run this in a fork to not contaminate
82
- # the main test environment
83
- capture_stderr do # redirect stderr
84
- pid = Process.fork do
85
- runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
86
- runner.run_file(cucumber_feature_file)
87
- assert File.exists?(target_file)
88
- assert_equal "HYDRA", File.read(target_file)
89
-
90
- FileUtils.rm_f(target_file)
91
-
92
- runner.run_file(alternate_cucumber_feature_file)
93
- assert File.exists?(alternate_target_file)
94
- assert_equal "HYDRA", File.read(alternate_target_file)
95
- assert !File.exists?(target_file)
96
- end
97
- Process.wait pid
98
- end
99
- end
100
-
101
- should "be able to run a runner over ssh" do
102
- ssh = Hydra::SSH.new(
103
- 'localhost',
104
- File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
105
- "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new, :verbose => true);\""
106
- )
107
- assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
108
- ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
109
-
110
- # grab its response. This makes us wait for it to finish
111
- echo = ssh.gets # get the ssh echo
112
- response = ssh.gets
113
-
114
- assert_equal Hydra::Messages::Runner::Results, response.class
115
-
116
- # tell it to shut down
117
- ssh.write(Hydra::Messages::Worker::Shutdown.new)
118
-
119
- ssh.close
120
-
121
- # ensure it ran
122
- assert File.exists?(target_file)
123
- assert_equal "HYDRA", File.read(target_file)
124
- end
125
-
126
- context "using runner events" do
127
- context "on successful termination" do
128
- setup do
129
- @pipe = Hydra::Pipe.new
130
- @parent = Process.fork do
131
- request_a_file_and_verify_completion(@pipe, test_file)
132
- end
133
- end
134
-
135
- should "fire runner_begin event" do
136
- run_the_runner(@pipe, [HydraExtension::RunnerListener::RunnerBeginTest.new] )
137
- Process.wait(@parent)
138
-
139
- # ensure runner_begin was fired
140
- assert_file_exists alternate_target_file
141
- end
142
-
143
- should "fire runner_end event" do
144
- run_the_runner(@pipe, [HydraExtension::RunnerListener::RunnerEndTest.new] )
145
- Process.wait(@parent)
146
-
147
- assert_file_exists alternate_target_file
148
- end
149
- end
150
-
151
- should "fire runner_end event after losing communication with worker" do
152
- pipe = Hydra::Pipe.new
153
- parent = Process.fork do
154
- pipe.identify_as_parent
155
-
156
- # grab its response.
157
- response = pipe.gets
158
- pipe.close #this will be detected by the runner and it should call runner_end
159
- end
160
-
161
- run_the_runner(pipe, [HydraExtension::RunnerListener::RunnerEndTest.new] )
162
- Process.wait(parent)
163
-
164
- # ensure runner_end was fired
165
- assert File.exists?( alternate_target_file )
166
- end
167
- end
168
- end
169
-
170
- module RunnerTestHelper
171
- def request_a_file_and_verify_completion(pipe, file)
172
- pipe.identify_as_parent
173
-
174
- # make sure it asks for a file, then give it one
175
- assert pipe.gets.is_a?(Hydra::Messages::Runner::RequestFile)
176
- pipe.write(Hydra::Messages::Worker::RunFile.new(:file => file))
177
-
178
- # grab its response. This makes us wait for it to finish
179
- response = pipe.gets
180
-
181
- # tell it to shut down
182
- pipe.write(Hydra::Messages::Worker::Shutdown.new)
183
-
184
- # ensure it ran
185
- assert File.exists?(target_file)
186
- assert_equal "HYDRA", File.read(target_file)
187
- end
188
-
189
- def run_the_runner(pipe, listeners = [])
190
- pipe.identify_as_child
191
- Hydra::Runner.new( :io => pipe, :runner_listeners => listeners )
192
- end
193
- end
194
- include RunnerTestHelper
195
- end
196
-
@@ -1,25 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
-
3
- class SSHTest < Test::Unit::TestCase
4
- should "be able to execute a command over ssh" do
5
- ssh = Hydra::SSH.new(
6
- 'localhost', # connect to this machine
7
- File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
8
- "ruby fixtures/hello_world.rb"
9
- )
10
- response = ssh.gets
11
- assert_equal "Hello World", response.text
12
- ssh.close
13
- end
14
-
15
- should "be able to handle a large number of non-Hydra console output" do
16
- ssh = Hydra::SSH.new(
17
- 'localhost', # connect to this machine
18
- File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
19
- "ruby fixtures/many_outputs_to_console.rb"
20
- )
21
- response = ssh.gets
22
- assert_equal "My message", response.text
23
- ssh.close
24
- end
25
- end
@@ -1,113 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
-
3
- class SyncTest < Test::Unit::TestCase
4
- context "with a file to test and a destination to verify" do
5
- setup do
6
- # avoid having other tests interfering with us
7
- sleep(0.2)
8
- #FileUtils.rm_f(target_file)
9
- end
10
-
11
- teardown do
12
- #FileUtils.rm_f(target_file)
13
- end
14
-
15
- should "synchronize a test file over ssh with rsync" do
16
- local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
17
- remote = File.join(Dir.consistent_tmpdir, 'hydra', 'remote')
18
- sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
19
- [local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
20
-
21
- # setup the folders:
22
- # local:
23
- # - test_a
24
- # - test_c
25
- # remote:
26
- # - test_b
27
- #
28
- # add test_c to exludes
29
- FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
30
- FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
31
- FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
32
-
33
- # ensure a is not on remote
34
- assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
35
- # ensure c is not on remote
36
- assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
37
- # ensure b is on remote
38
- assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
39
-
40
- Hydra::Sync.new(
41
- {
42
- :type => :ssh,
43
- :connect => 'localhost',
44
- :directory => remote,
45
- :runners => 1
46
- },
47
- {
48
- :directory => local,
49
- :exclude => ['test_c.rb']
50
- }
51
- )
52
- # ensure a is copied
53
- assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
54
- # ensure c is not copied
55
- assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
56
- # ensure b is deleted
57
- assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
58
- end
59
-
60
- should "synchronize a test file over ssh with rsync to multiple workers" do
61
- local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
62
- remote_a = File.join(Dir.consistent_tmpdir, 'hydra', 'remote_a')
63
- remote_b = File.join(Dir.consistent_tmpdir, 'hydra', 'remote_b')
64
- sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
65
- [local, remote_a, remote_b].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
66
-
67
- # setup the folders:
68
- # local:
69
- # - test_a
70
- # remote_a:
71
- # - test_b
72
- # remote_b:
73
- # - test_c
74
- #
75
- # add test_c to exludes
76
- FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
77
- FileUtils.cp(sync_test, File.join(remote_a, 'test_b.rb'))
78
- FileUtils.cp(sync_test, File.join(remote_b, 'test_c.rb'))
79
-
80
- # ensure a is not on remotes
81
- assert !File.exists?(File.join(remote_a, 'test_a.rb')), "A should not be on remote_a"
82
- assert !File.exists?(File.join(remote_b, 'test_a.rb')), "A should not be on remote_b"
83
- # ensure b is on remote_a
84
- assert File.exists?(File.join(remote_a, 'test_b.rb')), "B should be on remote_a"
85
- # ensure c is on remote_b
86
- assert File.exists?(File.join(remote_b, 'test_c.rb')), "C should be on remote_b"
87
-
88
- Hydra::Sync.sync_many(
89
- :workers => [{
90
- :type => :ssh,
91
- :connect => 'localhost',
92
- :directory => remote_a,
93
- :runners => 1
94
- },
95
- {
96
- :type => :ssh,
97
- :connect => 'localhost',
98
- :directory => remote_b,
99
- :runners => 1
100
- }],
101
- :sync => {
102
- :directory => local
103
- }
104
- )
105
- # ensure a is copied to both remotes
106
- assert File.exists?(File.join(remote_a, 'test_a.rb')), "A was not copied to remote_a"
107
- assert File.exists?(File.join(remote_b, 'test_a.rb')), "A was not copied to remote_b"
108
- # ensure b and c are deleted from remotes
109
- assert !File.exists?(File.join(remote_a, 'test_b.rb')), "B was not deleted from remote_a"
110
- assert !File.exists?(File.join(remote_b, 'test_c.rb')), "C was not deleted from remote_b"
111
- end
112
- end
113
- end
@@ -1,21 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
- require 'hydra/tasks'
3
- require 'rake'
4
-
5
- class TaskTest < Test::Unit::TestCase
6
- context "a task" do
7
- should "execute the command in a remote machine" do
8
-
9
- File.delete( "/tmp/new_file" ) if File.exists? "/tmp/new_file"
10
-
11
- Hydra::RemoteTask.new('cat:text_file', 'touch new_file') do |t|
12
- t.config = "test/fixtures/task_test_config.yml"
13
- end
14
-
15
- Rake.application['hydra:remote:cat:text_file'].invoke
16
-
17
- assert( File.exists? "/tmp/new_file" )
18
-
19
- end
20
- end
21
- end
@@ -1,107 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- gem 'shoulda', '2.10.3'
4
- gem 'rspec', '2.0.0.beta.19'
5
- require 'shoulda'
6
- require 'tmpdir'
7
- require "stringio"
8
-
9
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
- $LOAD_PATH.unshift(File.dirname(__FILE__))
11
- require 'hydra'
12
-
13
- # Since Hydra turns off testing, we have to turn it back on
14
- Test::Unit.run = false
15
-
16
- class Test::Unit::TestCase
17
- def target_file
18
- File.expand_path(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'))
19
- end
20
-
21
- def alternate_target_file
22
- File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
23
- end
24
-
25
- def test_file
26
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file.rb'))
27
- end
28
-
29
- def rspec_file
30
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_spec.rb'))
31
- end
32
-
33
- def alternate_rspec_file
34
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_alternate_spec.rb'))
35
- end
36
-
37
- def rspec_file_with_pending
38
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_with_pending_spec.rb'))
39
- end
40
-
41
- def cucumber_feature_file
42
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature'))
43
- end
44
-
45
- def alternate_cucumber_feature_file
46
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_alternate_file.feature'))
47
- end
48
-
49
- def javascript_file
50
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'js_file.js'))
51
- end
52
-
53
- def json_file
54
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'json_data.json'))
55
- end
56
-
57
- def conflicting_test_file
58
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'conflicting.rb'))
59
- end
60
-
61
- def remote_dir_path
62
- File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
63
- end
64
-
65
- def hydra_worker_init_file
66
- File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'hydra_worker_init.rb'))
67
- end
68
-
69
- def capture_stderr
70
- # The output stream must be an IO-like object. In this case we capture it in
71
- # an in-memory IO object so we can return the string value. You can assign any
72
- # IO object here.
73
- previous_stderr, $stderr = $stderr, StringIO.new
74
- yield
75
- $stderr.string
76
- ensure
77
- # Restore the previous value of stderr (typically equal to STDERR).
78
- $stderr = previous_stderr
79
- end
80
-
81
- #this method allow us to wait for a file for a maximum number of time, so the
82
- #test can pass in slower machines. This helps to speed up the tests
83
- def assert_file_exists file, time_to_wait = 2
84
- time_begin = Time.now
85
-
86
- until Time.now - time_begin >= time_to_wait or File.exists?( file ) do
87
- sleep 0.01
88
- end
89
-
90
- assert File.exists?( file )
91
- end
92
- end
93
-
94
- module Hydra #:nodoc:
95
- module Messages #:nodoc:
96
- class TestMessage < Hydra::Message
97
- attr_accessor :text
98
- def initialize(opts = {})
99
- @text = opts.fetch(:text){ "test" }
100
- end
101
- def serialize
102
- super(:text => @text)
103
- end
104
- end
105
- end
106
- end
107
-