auser-poolparty 0.2.66 → 0.2.67

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.
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.