auser-poolparty 0.2.66 → 0.2.67

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. data/Manifest.txt +83 -41
  2. data/PostInstall.txt +2 -2
  3. data/README.txt +1 -2
  4. data/Rakefile +14 -1
  5. data/bin/cloud-start +11 -10
  6. data/bin/{pool-spec → pool-generate} +0 -0
  7. data/bin/pool-init +3 -3
  8. data/bin/pool-start +8 -7
  9. data/bin/server-update-hosts +1 -1
  10. data/lib/erlang/messenger/ebin/pm_client_rel-0.1.rel +1 -1
  11. data/lib/erlang/messenger/ebin/pm_master_rel-0.1.rel +1 -1
  12. data/lib/erlang/messenger/ebin/pm_node_rel-0.1.rel +1 -1
  13. data/lib/erlang/messenger/include/defines.hrl +7 -3
  14. data/lib/erlang/messenger/lib/eunit/.svn/all-wcprops +53 -0
  15. data/lib/erlang/messenger/lib/eunit/.svn/entries +140 -0
  16. data/lib/erlang/messenger/lib/eunit/.svn/format +1 -0
  17. data/lib/erlang/messenger/lib/eunit/.svn/prop-base/NOTES.svn-base +5 -0
  18. data/lib/erlang/messenger/lib/eunit/.svn/text-base/AUTHORS.svn-base +2 -0
  19. data/lib/erlang/messenger/lib/eunit/.svn/text-base/CHANGELOG.svn-base +14 -0
  20. data/lib/erlang/messenger/lib/eunit/.svn/text-base/COPYING.svn-base +504 -0
  21. data/lib/erlang/messenger/lib/eunit/.svn/text-base/NOTES.svn-base +276 -0
  22. data/lib/erlang/messenger/lib/eunit/.svn/text-base/README.svn-base +3 -0
  23. data/lib/erlang/messenger/lib/eunit/.svn/text-base/sys.config.svn-base +9 -0
  24. data/lib/erlang/messenger/lib/eunit/.svn/text-base/vsn.mk.svn-base +1 -0
  25. data/lib/erlang/messenger/lib/eunit/doc/.svn/all-wcprops +59 -0
  26. data/lib/erlang/messenger/lib/eunit/doc/.svn/entries +142 -0
  27. data/lib/erlang/messenger/lib/eunit/doc/.svn/format +1 -0
  28. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/erlang.png.svn-base +5 -0
  29. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/eunit.html.svn-base +5 -0
  30. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/index.html.svn-base +5 -0
  31. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/modules-frame.html.svn-base +5 -0
  32. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/overview-summary.html.svn-base +5 -0
  33. data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/packages-frame.html.svn-base +5 -0
  34. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/edoc-info.svn-base +3 -0
  35. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/erlang.png.svn-base +0 -0
  36. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/eunit.html.svn-base +172 -0
  37. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/index.html.svn-base +17 -0
  38. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/modules-frame.html.svn-base +12 -0
  39. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/overview-summary.html.svn-base +984 -0
  40. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/overview.edoc.svn-base +980 -0
  41. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/packages-frame.html.svn-base +11 -0
  42. data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/stylesheet.css.svn-base +55 -0
  43. data/lib/erlang/messenger/lib/eunit/ebin/.svn/all-wcprops +5 -0
  44. data/lib/erlang/messenger/lib/eunit/ebin/.svn/dir-prop-base +8 -0
  45. data/lib/erlang/messenger/lib/eunit/ebin/.svn/entries +28 -0
  46. data/lib/erlang/messenger/lib/eunit/ebin/.svn/format +1 -0
  47. data/lib/erlang/messenger/lib/eunit/examples/.svn/all-wcprops +23 -0
  48. data/lib/erlang/messenger/lib/eunit/examples/.svn/entries +66 -0
  49. data/lib/erlang/messenger/lib/eunit/examples/.svn/format +1 -0
  50. data/lib/erlang/messenger/lib/eunit/examples/.svn/prop-base/eunit_examples.erl.svn-base +5 -0
  51. data/lib/erlang/messenger/lib/eunit/examples/.svn/prop-base/fib.erl.svn-base +5 -0
  52. data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/eunit_examples.erl.svn-base +339 -0
  53. data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/fib.erl.svn-base +19 -0
  54. data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/tests.txt.svn-base +1 -0
  55. data/lib/erlang/messenger/lib/eunit/include/.svn/all-wcprops +11 -0
  56. data/lib/erlang/messenger/lib/eunit/include/.svn/entries +41 -0
  57. data/lib/erlang/messenger/lib/eunit/include/.svn/format +1 -0
  58. data/lib/erlang/messenger/lib/eunit/include/.svn/prop-base/eunit.hrl.svn-base +5 -0
  59. data/lib/erlang/messenger/lib/eunit/include/.svn/text-base/eunit.hrl.svn-base +313 -0
  60. data/lib/erlang/messenger/lib/eunit/src/.svn/all-wcprops +113 -0
  61. data/lib/erlang/messenger/lib/eunit/src/.svn/entries +259 -0
  62. data/lib/erlang/messenger/lib/eunit/src/.svn/format +1 -0
  63. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/autoload.erl.svn-base +5 -0
  64. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/code_monitor.erl.svn-base +5 -0
  65. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit.erl.svn-base +5 -0
  66. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_autoexport.erl.svn-base +5 -0
  67. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_data.erl.svn-base +5 -0
  68. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_internal.hrl.svn-base +5 -0
  69. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_lib.erl.svn-base +5 -0
  70. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_proc.erl.svn-base +5 -0
  71. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_serial.erl.svn-base +5 -0
  72. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_server.erl.svn-base +5 -0
  73. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_striptests.erl.svn-base +5 -0
  74. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_test.erl.svn-base +5 -0
  75. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_tests.erl.svn-base +5 -0
  76. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_tty.erl.svn-base +5 -0
  77. data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/file_monitor.erl.svn-base +5 -0
  78. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/autoload.erl.svn-base +388 -0
  79. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/code_monitor.erl.svn-base +243 -0
  80. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.app.src.svn-base +21 -0
  81. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.appup.src.svn-base +1 -0
  82. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.erl.svn-base +196 -0
  83. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_autoexport.erl.svn-base +102 -0
  84. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_data.erl.svn-base +798 -0
  85. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_internal.hrl.svn-base +48 -0
  86. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_lib.erl.svn-base +682 -0
  87. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_proc.erl.svn-base +552 -0
  88. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_serial.erl.svn-base +157 -0
  89. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_server.erl.svn-base +340 -0
  90. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_striptests.erl.svn-base +64 -0
  91. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_test.erl.svn-base +334 -0
  92. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_tests.erl.svn-base +45 -0
  93. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_tty.erl.svn-base +272 -0
  94. data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/file_monitor.erl.svn-base +409 -0
  95. data/lib/erlang/messenger/pm_client_rel-0.1.boot +0 -0
  96. data/lib/erlang/messenger/pm_client_rel-0.1.script +77 -85
  97. data/lib/erlang/messenger/pm_master_rel-0.1.boot +0 -0
  98. data/lib/erlang/messenger/pm_master_rel-0.1.script +78 -85
  99. data/lib/erlang/messenger/pm_node_rel-0.1.boot +0 -0
  100. data/lib/erlang/messenger/pm_node_rel-0.1.script +77 -86
  101. data/lib/erlang/messenger/src/pm_node.erl +46 -9
  102. data/lib/erlang/messenger/src/utils.erl +7 -1
  103. data/lib/poolparty.rb +17 -23
  104. data/lib/poolparty/base_packages/poolparty.rb +1 -1
  105. data/lib/poolparty/core/string.rb +11 -2
  106. data/lib/poolparty/helpers/binary.rb +31 -0
  107. data/lib/poolparty/helpers/console.rb +25 -16
  108. data/lib/poolparty/helpers/nice_printer.rb +36 -0
  109. data/lib/poolparty/helpers/optioner.rb +8 -0
  110. data/lib/poolparty/helpers/provisioner_base.rb +7 -5
  111. data/lib/poolparty/helpers/provisioners/master.rb +1 -1
  112. data/lib/poolparty/helpers/provisioners/slave.rb +2 -1
  113. data/lib/poolparty/modules/cloud_resourcer.rb +1 -1
  114. data/lib/poolparty/modules/file_writer.rb +12 -1
  115. data/lib/poolparty/modules/resourcing_dsl.rb +2 -1
  116. data/lib/poolparty/monitors/base_monitor.rb +3 -0
  117. data/lib/poolparty/net/remoter.rb +13 -11
  118. data/lib/poolparty/pool/base.rb +25 -13
  119. data/lib/poolparty/pool/cloud.rb +32 -10
  120. data/lib/poolparty/pool/custom_resource.rb +16 -7
  121. data/lib/poolparty/pool/plugin_model.rb +2 -2
  122. data/lib/poolparty/pool/pool.rb +2 -2
  123. data/lib/poolparty/pool/resource.rb +25 -7
  124. data/lib/poolparty/pool/resources/class_package.rb +3 -2
  125. data/lib/poolparty/pool/resources/exec.rb +1 -1
  126. data/lib/poolparty/pool/resources/variable.rb +4 -0
  127. data/lib/poolparty/version.rb +1 -1
  128. data/poolparty.gemspec +13 -11
  129. data/spec/poolparty/core/hash_spec.rb +1 -1
  130. data/spec/poolparty/core/time_spec.rb +1 -1
  131. data/spec/poolparty/net/remote_spec.rb +1 -1
  132. data/spec/poolparty/pool/base_spec.rb +25 -20
  133. data/spec/poolparty/pool/cloud_spec.rb +50 -3
  134. data/spec/poolparty/pool/plugin_spec.rb +1 -0
  135. data/spec/poolparty/pool/resource_spec.rb +4 -3
  136. data/spec/poolparty/spec_helper.rb +3 -4
  137. data/tasks/deployment.rake +15 -3
  138. data/website/index.html +2 -2
  139. metadata +88 -46
  140. data/lib/erlang/messenger/Makefile +0 -15
  141. data/lib/erlang/messenger/lib/eunit/Makefile +0 -28
  142. data/lib/erlang/messenger/lib/eunit/ebin/autoload.beam +0 -0
  143. data/lib/erlang/messenger/lib/eunit/ebin/code_monitor.beam +0 -0
  144. data/lib/erlang/messenger/lib/eunit/ebin/eunit.beam +0 -0
  145. data/lib/erlang/messenger/lib/eunit/ebin/eunit_autoexport.beam +0 -0
  146. data/lib/erlang/messenger/lib/eunit/ebin/eunit_data.beam +0 -0
  147. data/lib/erlang/messenger/lib/eunit/ebin/eunit_lib.beam +0 -0
  148. data/lib/erlang/messenger/lib/eunit/ebin/eunit_proc.beam +0 -0
  149. data/lib/erlang/messenger/lib/eunit/ebin/eunit_serial.beam +0 -0
  150. data/lib/erlang/messenger/lib/eunit/ebin/eunit_server.beam +0 -0
  151. data/lib/erlang/messenger/lib/eunit/ebin/eunit_striptests.beam +0 -0
  152. data/lib/erlang/messenger/lib/eunit/ebin/eunit_test.beam +0 -0
  153. data/lib/erlang/messenger/lib/eunit/ebin/eunit_tests.beam +0 -0
  154. data/lib/erlang/messenger/lib/eunit/ebin/eunit_tty.beam +0 -0
  155. data/lib/erlang/messenger/lib/eunit/ebin/file_monitor.beam +0 -0
  156. data/lib/erlang/messenger/lib/eunit/src/Makefile +0 -46
  157. data/lib/poolparty/config/allowed_commands.yml +0 -1
  158. data/lib/poolparty/plugins/git.rb +0 -45
  159. data/spec/poolparty/plugins/git_spec.rb +0 -40
@@ -0,0 +1,552 @@
1
+ %% This library is free software; you can redistribute it and/or modify
2
+ %% it under the terms of the GNU Lesser General Public License as
3
+ %% published by the Free Software Foundation; either version 2 of the
4
+ %% License, or (at your option) any later version.
5
+ %%
6
+ %% This library is distributed in the hope that it will be useful, but
7
+ %% WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9
+ %% Lesser General Public License for more details.
10
+ %%
11
+ %% You should have received a copy of the GNU Lesser General Public
12
+ %% License along with this library; if not, write to the Free Software
13
+ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
14
+ %% USA
15
+ %%
16
+ %% $Id$
17
+ %%
18
+ %% @author Richard Carlsson <richardc@it.uu.se>
19
+ %% @copyright 2006 Richard Carlsson
20
+ %% @private
21
+ %% @see eunit
22
+ %% @doc Test runner process tree functions
23
+
24
+ -module(eunit_proc).
25
+
26
+ -include("eunit.hrl").
27
+ -include("eunit_internal.hrl").
28
+
29
+ -export([start/4]).
30
+
31
+
32
+ -record(procstate, {ref, id, super, insulator, parent, order}).
33
+
34
+
35
+ %% Spawns test process and returns the process Pid; sends {done,
36
+ %% Reference, Pid} to caller when finished. See the function
37
+ %% wait_for_task/2 for details about the need for the reference.
38
+ %%
39
+ %% The `Super' process receives a stream of status messages; see
40
+ %% status_message/3 for details.
41
+
42
+ start(Tests, Order, Super, Reference)
43
+ when is_pid(Super), is_reference(Reference) ->
44
+ St = #procstate{ref = Reference,
45
+ id = [],
46
+ super = Super,
47
+ order = Order},
48
+ spawn_group(local, #group{tests = Tests}, St).
49
+
50
+
51
+ %% Status messages sent to the supervisor process. (A supervisor does
52
+ %% not have to act on these messages - it can e.g. just log them, or
53
+ %% even discard them.) Each status message has the following form:
54
+ %%
55
+ %% {status, Id, Info}
56
+ %%
57
+ %% where Id identifies the item that the message pertains to, and the
58
+ %% Info part can be one of:
59
+ %%
60
+ %% {progress, 'begin', test | group}
61
+ %% indicates that the item has been entered, and what type it is
62
+ %%
63
+ %% {progress, 'end', {Status, Time::integer(), Output::io_list()}}
64
+ %% Status = 'ok' | {error, Exception} | {skipped, Cause}
65
+ %%
66
+ %% where Time is measured in milliseconds and Output is the data
67
+ %% written to the standard output stream during the test; if
68
+ %% Status is {skipped, Cause}, then Cause is a term thrown from
69
+ %% eunit_test:run_testfun/1
70
+ %%
71
+ %% {cancel, Descriptor}
72
+ %% where Descriptor can be:
73
+ %% timeout a timeout occurred
74
+ %% {blame, Id} forced to terminate because of item `Id'
75
+ %% {abort, Cause} the test failed to execute
76
+ %% {exit, Reason} the test process terminated unexpectedly
77
+ %% {startup, Reason} failed to start a remote test process
78
+ %%
79
+ %% where Cause is a term thrown from eunit_data:enter_context/4 or
80
+ %% from eunit_data:iter_next/2, and Reason is an exit term from a
81
+ %% crashed process
82
+ %%
83
+ %% Note that due to concurrent (and possibly distributed) execution,
84
+ %% there are *no* strict ordering guarantees on the status messages,
85
+ %% with one exception: a 'begin' message will always arrive before its
86
+ %% corresponding 'end' message.
87
+
88
+ status_message(Id, Info, St) ->
89
+ St#procstate.super ! {status, Id, Info}.
90
+
91
+
92
+ %% @TODO implement synchronized mode for insulator/child execution
93
+
94
+ %% Ideas for synchronized mode:
95
+ %%
96
+ %% * At each "program point", i.e., before entering a test, entering a
97
+ %% group, or leaving a group, the child will synchronize with the
98
+ %% insulator to make sure it is ok to proceed.
99
+ %%
100
+ %% * The insulator can receive controlling messages from higher up in
101
+ %% the hierarchy, telling it to pause, resume, single-step, repeat, etc.
102
+ %%
103
+ %% * Synchronization on entering/leaving groups is necessary in order to
104
+ %% get control over things such as subprocess creation/termination and
105
+ %% setup/cleanup, making it possible to, e.g., repeat all the tests
106
+ %% within a particular subprocess without terminating and restarting it,
107
+ %% or repeating tests without repeating the setup/cleanup.
108
+ %%
109
+ %% * Some tests that depend on state will not be possible to repeat, but
110
+ %% require a fresh context setup. There is nothing that can be done
111
+ %% about this, and the many tests that are repeatable should not be
112
+ %% punished because of it. The user must decide which level to restart.
113
+ %%
114
+ %% * Question: How propagate control messages down the hierarchy
115
+ %% (preferably only to the correct insulator process)? An insulator does
116
+ %% not currenctly know whether its child process has spawned subtasks.
117
+ %% (The "supervisor" process does not know the Pids of the controlling
118
+ %% insulator processes in the tree, and it probably should not be
119
+ %% responsible for this anyway.)
120
+
121
+
122
+ %% ---------------------------------------------------------------------
123
+ %% Process tree primitives
124
+
125
+ %% A "task" consists of an insulator process and a child process which
126
+ %% handles the actual work. When the child terminates, the insulator
127
+ %% process sends {done, Reference, self()} to the process which started
128
+ %% the task (the "parent"). The child process is given a State record
129
+ %% which contains the process id:s of the parent, the insulator, and the
130
+ %% supervisor.
131
+
132
+ %% @spec (Type, (#procstate{}) -> () -> term(), #procstate{}) -> pid()
133
+ %% Type = local | {remote, Node::atom()}
134
+
135
+ start_task(Type, Fun, St0) ->
136
+ St = St0#procstate{parent = self()},
137
+ %% (note: the link here is mainly to propagate signals *downwards*,
138
+ %% so that the insulator can detect if the process that started the
139
+ %% task dies before the task is done)
140
+ F = fun () -> insulator_process(Type, Fun, St) end,
141
+ case Type of
142
+ local ->
143
+ %% we assume (at least for now) that local spawns can never
144
+ %% fail in such a way that the process does not start, so a
145
+ %% new local insulator does not need to synchronize here
146
+ spawn_link(F);
147
+ {remote, Node} ->
148
+ Pid = spawn_link(Node, F),
149
+ %% See below for the need for the {ok, Reference, Pid}
150
+ %% message.
151
+ Reference = St#procstate.ref,
152
+ Monitor = erlang:monitor(process, Pid),
153
+ %% (the DOWN message is guaranteed to arrive after any
154
+ %% messages sent by the process itself)
155
+ receive
156
+ {ok, Reference, Pid} ->
157
+ Pid;
158
+ {'DOWN', Monitor, process, Pid, Reason} ->
159
+ %% send messages as if the insulator process was
160
+ %% started, but terminated on its own accord
161
+ Msg = {startup, Reason},
162
+ status_message(St#procstate.id, {cancel, Msg}, St),
163
+ self() ! {done, Reference, Pid}
164
+ end,
165
+ erlang:demonitor(Monitor, [flush]),
166
+ Pid
167
+ end.
168
+
169
+ %% Relatively simple, and hopefully failure-proof insulator process
170
+ %% (This is cleaner than temporarily setting up the caller to trap
171
+ %% signals, and does not affect the caller's mailbox or other state.)
172
+ %%
173
+ %% We assume that nobody does a 'kill' on an insulator process - if that
174
+ %% should happen, the test framework will hang since the insulator will
175
+ %% never send a reply; see below for more.
176
+ %%
177
+ %% Note that even if the insulator process itself never fails, it is
178
+ %% still possible that it does not start properly, if it is spawned
179
+ %% remotely (e.g., if the remote node is down). Therefore, remote
180
+ %% insulators must always immediately send an {ok, Reference, self()}
181
+ %% message to the parent as soon as it is spawned.
182
+
183
+ %% @spec (Type, Fun::() -> term(), St::#procstate{}) -> ok
184
+ %% Type = local | {remote, Node::atom()}
185
+
186
+ insulator_process(Type, Fun, St0) ->
187
+ process_flag(trap_exit, true),
188
+ Parent = St0#procstate.parent,
189
+ if Type == local -> ok;
190
+ true -> Parent ! {ok, St0#procstate.ref, self()}
191
+ end,
192
+ St = St0#procstate{insulator = self()},
193
+ Child = spawn_link(fun () -> child_process(Fun(St), St) end),
194
+ insulator_wait(Child, Parent, [], St).
195
+
196
+ %% Normally, child processes exit with the reason 'normal' even if the
197
+ %% executed tests failed (by throwing exceptions), since the tests are
198
+ %% executed within a try-block. Child processes can terminate abnormally
199
+ %% by the following reasons:
200
+ %% 1) an error in the processing of the test descriptors (a malformed
201
+ %% descriptor, failure in a setup, cleanup or initialization, a
202
+ %% missing module or function, or a failing generator function);
203
+ %% 2) an internal error in the test running framework itself;
204
+ %% 3) receiving a non-trapped error signal as a consequence of running
205
+ %% test code.
206
+ %% Those under point 1 are "expected errors", handled specially in the
207
+ %% protocol, while the other two are unexpected errors. (Since alt. 3
208
+ %% implies that the test neither reported success nor failure, it can
209
+ %% never be considered "proper" behaviour of a test.) Abnormal
210
+ %% termination is reported to the supervisor process but otherwise does
211
+ %% not affect the insulator compared to normal termination. Child
212
+ %% processes can also be killed abruptly by their insulators, in case of
213
+ %% a timeout or if a parent process dies.
214
+ %%
215
+ %% The insulator is the group leader for the child process, and gets all
216
+ %% of its standard I/O. The output is buffered and associated with the
217
+ %% currently active test or group, and is sent along with the 'end'
218
+ %% progress message when the test or group has finished.
219
+
220
+ insulator_wait(Child, Parent, Buf, St) ->
221
+ receive
222
+ {io_request, From, ReplyAs, Req} when is_pid(From) ->
223
+ Buf1 = io_request(From, ReplyAs, Req, hd(Buf)),
224
+ insulator_wait(Child, Parent, [Buf1 | tl(Buf)], St);
225
+ {progress, Child, Id, 'begin', Class} ->
226
+ status_message(Id, {progress, 'begin', Class}, St),
227
+ insulator_wait(Child, Parent, [[] | Buf], St);
228
+ {progress, Child, Id, 'end', {Status, Time}} ->
229
+ Msg = {Status, Time, lists:reverse(hd(Buf))},
230
+ status_message(Id, {progress, 'end', Msg}, St),
231
+ insulator_wait(Child, Parent, tl(Buf), St);
232
+ {cancel, Child, Id, Reason} ->
233
+ status_message(Id, {cancel, Reason}, St),
234
+ insulator_wait(Child, Parent, Buf, St);
235
+ {abort, Child, Id, Cause} ->
236
+ exit_messages(Id, {abort, Cause}, St),
237
+ %% no need to wait for the {'EXIT',Child,_} message
238
+ terminate_insulator(St);
239
+ {timeout, Child, Id} ->
240
+ exit_messages(Id, timeout, St),
241
+ kill_task(Child, St);
242
+ {'EXIT', Child, normal} ->
243
+ terminate_insulator(St);
244
+ {'EXIT', Child, Reason} ->
245
+ exit_messages(St#procstate.id, {exit, Reason}, St),
246
+ terminate_insulator(St);
247
+ {'EXIT', Parent, _} ->
248
+ %% make sure child processes are cleaned up recursively
249
+ kill_task(Child, St)
250
+ end.
251
+
252
+ kill_task(Child, St) ->
253
+ exit(Child, kill),
254
+ terminate_insulator(St).
255
+
256
+ %% Unlinking before exit avoids polluting the parent process with exit
257
+ %% signals from the insulator. The child process is already dead here.
258
+
259
+ terminate_insulator(St) ->
260
+ %% messaging/unlinking is ok even if the parent is already dead
261
+ Parent = St#procstate.parent,
262
+ Parent ! {done, St#procstate.ref, self()},
263
+ unlink(Parent),
264
+ exit(normal).
265
+
266
+ %% send cancel messages for the Id of the "causing" item, and also for
267
+ %% the Id of the insulator itself, if they are different
268
+ exit_messages(Id, Cause, St) ->
269
+ %% the message for the most specific Id is always sent first
270
+ status_message(Id, {cancel, Cause}, St),
271
+ case St#procstate.id of
272
+ Id -> ok;
273
+ Id1 -> status_message(Id1, {cancel, {blame, Id}}, St)
274
+ end.
275
+
276
+ %% Child processes send all messages via the insulator to ensure proper
277
+ %% sequencing with timeouts and exit signals.
278
+
279
+ abort_message(Cause, St) ->
280
+ St#procstate.insulator ! {abort, self(), St#procstate.id, Cause}.
281
+
282
+ cancel_message(Msg, St) ->
283
+ St#procstate.insulator ! {cancel, self(), St#procstate.id, Msg}.
284
+
285
+ progress_message(Type, Data, St) ->
286
+ St#procstate.insulator ! {progress, self(), St#procstate.id,
287
+ Type, Data}.
288
+
289
+ %% Timeout handling
290
+
291
+ set_timeout(Time, St) ->
292
+ erlang:send_after(Time, St#procstate.insulator,
293
+ {timeout, self(), St#procstate.id}).
294
+
295
+ clear_timeout(Ref) ->
296
+ erlang:cancel_timer(Ref).
297
+
298
+ with_timeout(undefined, Default, F, St) ->
299
+ with_timeout(Default, F, St);
300
+ with_timeout(Time, _Default, F, St) ->
301
+ with_timeout(Time, F, St).
302
+
303
+ with_timeout(infinity, F, _St) ->
304
+ %% don't start timers unnecessarily
305
+ {T0, _} = statistics(wall_clock),
306
+ Value = F(),
307
+ {T1, _} = statistics(wall_clock),
308
+ {Value, T1 - T0};
309
+ with_timeout(Time, F, St) when is_integer(Time), Time > 16#FFFFffff ->
310
+ with_timeout(16#FFFFffff, F, St);
311
+ with_timeout(Time, F, St) when is_integer(Time), Time < 0 ->
312
+ with_timeout(0, F, St);
313
+ with_timeout(Time, F, St) when is_integer(Time) ->
314
+ Ref = set_timeout(Time, St),
315
+ {T0, _} = statistics(wall_clock),
316
+ try F() of
317
+ Value ->
318
+ %% we could also read the timer, but this is simpler
319
+ {T1, _} = statistics(wall_clock),
320
+ {Value, T1 - T0}
321
+ after
322
+ clear_timeout(Ref)
323
+ end.
324
+
325
+ %% The normal behaviour of a child process is to trap exit signals. This
326
+ %% makes it easier to write tests that spawn off separate (linked)
327
+ %% processes and test whether they terminate as expected. The testing
328
+ %% framework is not dependent on this, however, so the test code is
329
+ %% allowed to disable signal trapping as it pleases.
330
+ %% Note that I/O is redirected to the insulator process.
331
+
332
+ %% @spec (() -> term(), #procstate{}) -> ok
333
+
334
+ child_process(Fun, St) ->
335
+ process_flag(trap_exit, true),
336
+ group_leader(St#procstate.insulator, self()),
337
+ try Fun() of
338
+ _ -> ok
339
+ catch
340
+ %% the only "normal" way for a child process to bail out is to
341
+ %% throw an {eunit_abort, Reason} exception; any other exception
342
+ %% will be reported as an unexpected termination of the test
343
+ {eunit_abort, Cause} ->
344
+ abort_message(Cause, St),
345
+ exit(aborted)
346
+ end.
347
+
348
+ %% @throws abortException()
349
+ %% @type abortException() = {abort, Cause::term()}
350
+
351
+ abort_task(Cause) ->
352
+ throw({eunit_abort, Cause}).
353
+
354
+ %% Typically, the process that executes this code is trapping signals,
355
+ %% but it might not be - it is outside of our control, since test code
356
+ %% could turn off trapping. That is why the insulator process of a task
357
+ %% must be guaranteed to always send a reply before it terminates.
358
+ %%
359
+ %% The unique reference guarantees that we don't extract any message
360
+ %% from the mailbox unless it belongs to the test framework (and not to
361
+ %% the running tests) - it is not possible to use selective receive to
362
+ %% match only messages tagged with some pid in a dynamically varying set
363
+ %% of pids. When the wait-loop terminates, no such message should remain
364
+ %% in the mailbox.
365
+
366
+ wait_for_task(Pid, St) ->
367
+ wait_for_tasks(sets:from_list([Pid]), St).
368
+
369
+ wait_for_tasks(PidSet, St) ->
370
+ case sets:size(PidSet) of
371
+ 0 ->
372
+ ok;
373
+ _ ->
374
+ %% (note that when we receive this message for some task, we
375
+ %% are guaranteed that the insulator process of the task has
376
+ %% already informed the supervisor about any anomalies)
377
+ Reference = St#procstate.ref,
378
+ receive
379
+ {done, Reference, Pid} ->
380
+ %% (if Pid is not in the set, del_element has no
381
+ %% effect, so this is always safe)
382
+ Rest = sets:del_element(Pid, PidSet),
383
+ wait_for_tasks(Rest, St)
384
+ end
385
+ end.
386
+
387
+
388
+ %% ---------------------------------------------------------------------
389
+ %% Separate testing process
390
+
391
+ tests(T, St) ->
392
+ I = eunit_data:iter_init(T, St#procstate.id),
393
+ case St#procstate.order of
394
+ inorder -> tests_inorder(I, St);
395
+ inparallel -> tests_inparallel(I, 0, St);
396
+ {inparallel, N} when is_integer(N), N >= 0 ->
397
+ tests_inparallel(I, N, St)
398
+ end.
399
+
400
+ set_id(I, St) ->
401
+ St#procstate{id = eunit_data:iter_id(I)}.
402
+
403
+ tests_inorder(I, St) ->
404
+ tests_inorder(I, 0, St).
405
+
406
+ tests_inorder(I, N, St) ->
407
+ case get_next_item(I) of
408
+ {T, I1} ->
409
+ handle_item(T, set_id(I1, St)),
410
+ tests_inorder(I1, N+1, St);
411
+ none ->
412
+ N
413
+ end.
414
+
415
+ tests_inparallel(I, K0, St) ->
416
+ tests_inparallel(I, 0, St, K0, K0, sets:new()).
417
+
418
+ tests_inparallel(I, N, St, K, K0, Children) when K =< 0, K0 > 0 ->
419
+ wait_for_tasks(Children, St),
420
+ tests_inparallel(I, N, St, K0, K0, sets:new());
421
+ tests_inparallel(I, N, St, K, K0, Children) ->
422
+ case get_next_item(I) of
423
+ {T, I1} ->
424
+ Child = spawn_item(T, set_id(I1, St)),
425
+ tests_inparallel(I1, N+1, St, K - 1, K0,
426
+ sets:add_element(Child, Children));
427
+ none ->
428
+ wait_for_tasks(Children, St),
429
+ N
430
+ end.
431
+
432
+ spawn_item(T, St0) ->
433
+ Fun = fun (St) ->
434
+ fun () -> handle_item(T, St) end
435
+ end,
436
+ %% inparallel-items are always spawned locally
437
+ start_task(local, Fun, St0).
438
+
439
+ get_next_item(I) ->
440
+ eunit_data:iter_next(I, fun abort_task/1).
441
+
442
+ handle_item(T, St) ->
443
+ case T of
444
+ #test{} -> handle_test(T, St);
445
+ #group{} -> handle_group(T, St)
446
+ end.
447
+
448
+ handle_test(T, St) ->
449
+ progress_message('begin', test, St),
450
+ {Status, Time} = with_timeout(T#test.timeout, ?DEFAULT_TEST_TIMEOUT,
451
+ fun () -> run_test(T) end, St),
452
+ progress_message('end', {Status, Time}, St),
453
+ ok.
454
+
455
+ %% @spec (#test{}) -> ok | {error, eunit_lib:exception()}
456
+ %% | {skipped, eunit_test:wrapperError()}
457
+
458
+ run_test(#test{f = F}) ->
459
+ try eunit_test:run_testfun(F) of
460
+ {ok, _Value} ->
461
+ %% just throw away the return value
462
+ ok;
463
+ {error, Exception} ->
464
+ {error, Exception}
465
+ catch
466
+ throw:WrapperError -> {skipped, WrapperError}
467
+ end.
468
+
469
+ set_group_order(#group{order = undefined}, St) ->
470
+ St;
471
+ set_group_order(#group{order = Order}, St) ->
472
+ St#procstate{order = Order}.
473
+
474
+ handle_group(T, St0) ->
475
+ St = set_group_order(T, St0),
476
+ case T#group.spawn of
477
+ undefined ->
478
+ run_group(T, St);
479
+ Type ->
480
+ Child = spawn_group(Type, T, St),
481
+ wait_for_task(Child, St)
482
+ end.
483
+
484
+ spawn_group(Type, T, St0) ->
485
+ Fun = fun (St) ->
486
+ fun () -> run_group(T, St) end
487
+ end,
488
+ start_task(Type, Fun, St0).
489
+
490
+ run_group(T, St) ->
491
+ %% note that the setup/cleanup is outside the group timeout; if the
492
+ %% setup fails, we do not start any timers
493
+ Timeout = T#group.timeout,
494
+ progress_message('begin', group, St),
495
+ F = fun (T) -> enter_group(T, Timeout, St) end,
496
+ try with_context(T, F) of
497
+ {Status, Time} ->
498
+ progress_message('end', {Status, Time}, St)
499
+ catch
500
+ throw:Cause ->
501
+ cancel_message({abort, Cause}, St)
502
+ end,
503
+ ok.
504
+
505
+ enter_group(T, Timeout, St) ->
506
+ with_timeout(Timeout, ?DEFAULT_GROUP_TIMEOUT,
507
+ fun () -> tests(T, St) end, St).
508
+
509
+ with_context(#group{context = undefined, tests = T}, F) ->
510
+ F(T);
511
+ with_context(#group{context = #context{} = C, tests = I}, F) ->
512
+ eunit_data:enter_context(C, I, F).
513
+
514
+ %% Implementation of buffering I/O for the insulator process. (Note that
515
+ %% each batch of characters is just pushed on the buffer, so it needs to
516
+ %% be reversed when it is flushed.)
517
+
518
+ io_request(From, ReplyAs, Req, Buf) ->
519
+ {Reply, Buf1} = io_request(Req, Buf),
520
+ io_reply(From, ReplyAs, Reply),
521
+ Buf1.
522
+
523
+ io_reply(From, ReplyAs, Reply) ->
524
+ From ! {io_reply, ReplyAs, Reply}.
525
+
526
+ io_request({put_chars, Chars}, Buf) ->
527
+ {ok, [Chars | Buf]};
528
+ io_request({put_chars, M, F, As}, Buf) ->
529
+ try apply(M, F, As) of
530
+ Chars -> {ok, [Chars | Buf]}
531
+ catch
532
+ C:T -> {{error, {C,T,erlang:get_stacktrace()}}, Buf}
533
+ end;
534
+ io_request({get_chars, _Prompt, _N}, Buf) ->
535
+ {eof, Buf};
536
+ io_request({get_chars, _Prompt, _M, _F, _Xs}, Buf) ->
537
+ {eof, Buf};
538
+ io_request({get_line, _Prompt}, Buf) ->
539
+ {eof, Buf};
540
+ io_request({get_until, _Prompt, _M, _F, _As}, Buf) ->
541
+ {eof, Buf};
542
+ io_request({setopts, _Opts}, Buf) ->
543
+ {ok, Buf};
544
+ io_request({requests, Reqs}, Buf) ->
545
+ io_requests(Reqs, {ok, Buf});
546
+ io_request(_, Buf) ->
547
+ {{error, request}, Buf}.
548
+
549
+ io_requests([R | Rs], {ok, Buf}) ->
550
+ io_requests(Rs, io_request(R, Buf));
551
+ io_requests(_, Result) ->
552
+ Result.