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.
- data/Manifest.txt +83 -41
- data/PostInstall.txt +2 -2
- data/README.txt +1 -2
- data/Rakefile +14 -1
- data/bin/cloud-start +11 -10
- data/bin/{pool-spec → pool-generate} +0 -0
- data/bin/pool-init +3 -3
- data/bin/pool-start +8 -7
- data/bin/server-update-hosts +1 -1
- data/lib/erlang/messenger/ebin/pm_client_rel-0.1.rel +1 -1
- data/lib/erlang/messenger/ebin/pm_master_rel-0.1.rel +1 -1
- data/lib/erlang/messenger/ebin/pm_node_rel-0.1.rel +1 -1
- data/lib/erlang/messenger/include/defines.hrl +7 -3
- data/lib/erlang/messenger/lib/eunit/.svn/all-wcprops +53 -0
- data/lib/erlang/messenger/lib/eunit/.svn/entries +140 -0
- data/lib/erlang/messenger/lib/eunit/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/.svn/prop-base/NOTES.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/AUTHORS.svn-base +2 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/CHANGELOG.svn-base +14 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/COPYING.svn-base +504 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/NOTES.svn-base +276 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/README.svn-base +3 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/sys.config.svn-base +9 -0
- data/lib/erlang/messenger/lib/eunit/.svn/text-base/vsn.mk.svn-base +1 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/all-wcprops +59 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/entries +142 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/erlang.png.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/eunit.html.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/index.html.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/modules-frame.html.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/overview-summary.html.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/prop-base/packages-frame.html.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/edoc-info.svn-base +3 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/erlang.png.svn-base +0 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/eunit.html.svn-base +172 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/index.html.svn-base +17 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/modules-frame.html.svn-base +12 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/overview-summary.html.svn-base +984 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/overview.edoc.svn-base +980 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/packages-frame.html.svn-base +11 -0
- data/lib/erlang/messenger/lib/eunit/doc/.svn/text-base/stylesheet.css.svn-base +55 -0
- data/lib/erlang/messenger/lib/eunit/ebin/.svn/all-wcprops +5 -0
- data/lib/erlang/messenger/lib/eunit/ebin/.svn/dir-prop-base +8 -0
- data/lib/erlang/messenger/lib/eunit/ebin/.svn/entries +28 -0
- data/lib/erlang/messenger/lib/eunit/ebin/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/all-wcprops +23 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/entries +66 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/prop-base/eunit_examples.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/prop-base/fib.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/eunit_examples.erl.svn-base +339 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/fib.erl.svn-base +19 -0
- data/lib/erlang/messenger/lib/eunit/examples/.svn/text-base/tests.txt.svn-base +1 -0
- data/lib/erlang/messenger/lib/eunit/include/.svn/all-wcprops +11 -0
- data/lib/erlang/messenger/lib/eunit/include/.svn/entries +41 -0
- data/lib/erlang/messenger/lib/eunit/include/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/include/.svn/prop-base/eunit.hrl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/include/.svn/text-base/eunit.hrl.svn-base +313 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/all-wcprops +113 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/entries +259 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/format +1 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/autoload.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/code_monitor.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_autoexport.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_data.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_internal.hrl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_lib.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_proc.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_serial.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_server.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_striptests.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_test.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_tests.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/eunit_tty.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/prop-base/file_monitor.erl.svn-base +5 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/autoload.erl.svn-base +388 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/code_monitor.erl.svn-base +243 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.app.src.svn-base +21 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.appup.src.svn-base +1 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit.erl.svn-base +196 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_autoexport.erl.svn-base +102 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_data.erl.svn-base +798 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_internal.hrl.svn-base +48 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_lib.erl.svn-base +682 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_proc.erl.svn-base +552 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_serial.erl.svn-base +157 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_server.erl.svn-base +340 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_striptests.erl.svn-base +64 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_test.erl.svn-base +334 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_tests.erl.svn-base +45 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/eunit_tty.erl.svn-base +272 -0
- data/lib/erlang/messenger/lib/eunit/src/.svn/text-base/file_monitor.erl.svn-base +409 -0
- data/lib/erlang/messenger/pm_client_rel-0.1.boot +0 -0
- data/lib/erlang/messenger/pm_client_rel-0.1.script +77 -85
- data/lib/erlang/messenger/pm_master_rel-0.1.boot +0 -0
- data/lib/erlang/messenger/pm_master_rel-0.1.script +78 -85
- data/lib/erlang/messenger/pm_node_rel-0.1.boot +0 -0
- data/lib/erlang/messenger/pm_node_rel-0.1.script +77 -86
- data/lib/erlang/messenger/src/pm_node.erl +46 -9
- data/lib/erlang/messenger/src/utils.erl +7 -1
- data/lib/poolparty.rb +17 -23
- data/lib/poolparty/base_packages/poolparty.rb +1 -1
- data/lib/poolparty/core/string.rb +11 -2
- data/lib/poolparty/helpers/binary.rb +31 -0
- data/lib/poolparty/helpers/console.rb +25 -16
- data/lib/poolparty/helpers/nice_printer.rb +36 -0
- data/lib/poolparty/helpers/optioner.rb +8 -0
- data/lib/poolparty/helpers/provisioner_base.rb +7 -5
- data/lib/poolparty/helpers/provisioners/master.rb +1 -1
- data/lib/poolparty/helpers/provisioners/slave.rb +2 -1
- data/lib/poolparty/modules/cloud_resourcer.rb +1 -1
- data/lib/poolparty/modules/file_writer.rb +12 -1
- data/lib/poolparty/modules/resourcing_dsl.rb +2 -1
- data/lib/poolparty/monitors/base_monitor.rb +3 -0
- data/lib/poolparty/net/remoter.rb +13 -11
- data/lib/poolparty/pool/base.rb +25 -13
- data/lib/poolparty/pool/cloud.rb +32 -10
- data/lib/poolparty/pool/custom_resource.rb +16 -7
- data/lib/poolparty/pool/plugin_model.rb +2 -2
- data/lib/poolparty/pool/pool.rb +2 -2
- data/lib/poolparty/pool/resource.rb +25 -7
- data/lib/poolparty/pool/resources/class_package.rb +3 -2
- data/lib/poolparty/pool/resources/exec.rb +1 -1
- data/lib/poolparty/pool/resources/variable.rb +4 -0
- data/lib/poolparty/version.rb +1 -1
- data/poolparty.gemspec +13 -11
- data/spec/poolparty/core/hash_spec.rb +1 -1
- data/spec/poolparty/core/time_spec.rb +1 -1
- data/spec/poolparty/net/remote_spec.rb +1 -1
- data/spec/poolparty/pool/base_spec.rb +25 -20
- data/spec/poolparty/pool/cloud_spec.rb +50 -3
- data/spec/poolparty/pool/plugin_spec.rb +1 -0
- data/spec/poolparty/pool/resource_spec.rb +4 -3
- data/spec/poolparty/spec_helper.rb +3 -4
- data/tasks/deployment.rake +15 -3
- data/website/index.html +2 -2
- metadata +88 -46
- data/lib/erlang/messenger/Makefile +0 -15
- data/lib/erlang/messenger/lib/eunit/Makefile +0 -28
- data/lib/erlang/messenger/lib/eunit/ebin/autoload.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/code_monitor.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_autoexport.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_data.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_lib.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_proc.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_serial.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_server.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_striptests.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_test.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_tests.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/eunit_tty.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/ebin/file_monitor.beam +0 -0
- data/lib/erlang/messenger/lib/eunit/src/Makefile +0 -46
- data/lib/poolparty/config/allowed_commands.yml +0 -1
- data/lib/poolparty/plugins/git.rb +0 -45
- 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.
|