Skip to content

Commit 0e263c5

Browse files
committed
---
yaml --- r: 4156 b: refs/heads/master c: bcb5c4d h: refs/heads/master v: v3
1 parent 9abc78e commit 0e263c5

File tree

4 files changed

+164
-69
lines changed

4 files changed

+164
-69
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
---
2-
refs/heads/master: 1c780b420308cc9f57a5cb5b9619d43c1a4e6f44
2+
refs/heads/master: bcb5c4d54f601d6066d6fa463390d3d8b30f53b7

trunk/src/lib/test.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ export tr_ok;
1616
export tr_failed;
1717
export tr_ignored;
1818
export run_tests_console;
19+
export run_tests_console_;
1920
export run_test;
2021
export filter_tests;
2122
export parse_opts;
23+
export test_to_task;
24+
export default_test_to_task;
2225

2326
// The name of a test. By convention this follows the rules for rust
2427
// paths, i.e it should be a series of identifiers seperated by double
@@ -95,8 +98,21 @@ tag test_result {
9598
tr_ignored;
9699
}
97100

101+
// To get isolation and concurrency tests have to be run in their own tasks.
102+
// In cases where test functions and closures it is not ok to just dump them
103+
// into a task and run them, so this transformation gives the caller a chance
104+
// to create the test task.
105+
type test_to_task = fn(&fn()) -> task;
106+
98107
// A simple console test runner
99-
fn run_tests_console(&test_opts opts, &test_desc[] tests) -> bool {
108+
fn run_tests_console(&test_opts opts,
109+
&test_desc[] tests) -> bool {
110+
run_tests_console_(opts, tests, default_test_to_task)
111+
}
112+
113+
fn run_tests_console_(&test_opts opts,
114+
&test_desc[] tests,
115+
&test_to_task to_task) -> bool {
100116

101117
auto filtered_tests = filter_tests(opts, tests);
102118

@@ -124,7 +140,7 @@ fn run_tests_console(&test_opts opts, &test_desc[] tests) -> bool {
124140
while (wait_idx < total) {
125141
while (ivec::len(futures) < concurrency
126142
&& run_idx < total) {
127-
futures += ~[run_test(filtered_tests.(run_idx))];
143+
futures += ~[run_test(filtered_tests.(run_idx), to_task)];
128144
run_idx += 1u;
129145
}
130146

@@ -266,14 +282,14 @@ type test_future = rec(test_desc test,
266282
@fn() fnref,
267283
fn() -> test_result wait);
268284

269-
fn run_test(&test_desc test) -> test_future {
285+
fn run_test(&test_desc test, &test_to_task to_task) -> test_future {
270286
// FIXME: Because of the unsafe way we're passing the test function
271287
// to the test task, we need to make sure we keep a reference to that
272288
// function around for longer than the lifetime of the task. To that end
273289
// we keep the function boxed in the test future.
274290
auto fnref = @test.fn;
275291
if (!test.ignore) {
276-
auto test_task = run_test_fn_in_task(*fnref);
292+
auto test_task = to_task(*fnref);
277293
ret rec(test = test,
278294
fnref = fnref,
279295
wait = bind fn(&task test_task) -> test_result {
@@ -295,8 +311,9 @@ native "rust" mod rustrt {
295311

296312
// We need to run our tests in another task in order to trap test failures.
297313
// But, at least currently, functions can't be used as spawn arguments so
298-
// we've got to treat our test functions as unsafe pointers.
299-
fn run_test_fn_in_task(&fn() f) -> task {
314+
// we've got to treat our test functions as unsafe pointers. This function
315+
// only works with functions that don't contain closures.
316+
fn default_test_to_task(&fn() f) -> task {
300317
fn run_task(*mutable fn() fptr) {
301318
// If this task fails we don't want that failure to propagate to the
302319
// test runner or else we couldn't keep running tests

trunk/src/test/compiletest/compiletest.rs

Lines changed: 138 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,7 @@ fn parse_config(&str[] args) -> config {
8787
src_base = getopts::opt_str(match, "src-base"),
8888
build_base = getopts::opt_str(match, "build-base"),
8989
stage_id = getopts::opt_str(match, "stage-id"),
90-
mode = alt getopts::opt_str(match, "mode") {
91-
"compile-fail" { mode_compile_fail }
92-
"run-fail" { mode_run_fail }
93-
"run-pass" { mode_run_pass }
94-
_ { fail "invalid mode" }
95-
},
90+
mode = str_mode(getopts::opt_str(match, "mode")),
9691
run_ignored = getopts::opt_present(match, "ignored"),
9792
filter = if vec::len(match.free) > 0u {
9893
option::some(match.free.(0))
@@ -115,22 +110,37 @@ fn log_config(&config config) {
115110
logv(c, #fmt("stage_id: %s", config.stage_id));
116111
logv(c, #fmt("mode: %s", mode_str(config.mode)));
117112
logv(c, #fmt("run_ignored: %b", config.run_ignored));
118-
logv(c, #fmt("filter: %s", alt (config.filter) {
119-
option::some(?f) { f }
120-
option::none { "(none)" }
121-
}));
122-
logv(c, #fmt("runtool: %s", alt (config.runtool) {
123-
option::some(?s) { s }
124-
option::none { "(none)" }
125-
}));
126-
logv(c, #fmt("rustcflags: %s", alt (config.rustcflags) {
127-
option::some(?s) { s }
128-
option::none { "(none)" }
129-
}));
113+
logv(c, #fmt("filter: %s", opt_str(config.filter)));
114+
logv(c, #fmt("runtool: %s", opt_str(config.runtool)));
115+
logv(c, #fmt("rustcflags: %s", opt_str(config.rustcflags)));
130116
logv(c, #fmt("verbose: %b", config.verbose));
131117
logv(c, #fmt("\n"));
132118
}
133119

120+
fn opt_str(option::t[str] maybestr) -> str {
121+
alt maybestr {
122+
option::some(?s) { s }
123+
option::none { "(none)" }
124+
}
125+
}
126+
127+
fn str_opt(str maybestr) -> option::t[str] {
128+
if maybestr != "(none)" {
129+
option::some(maybestr)
130+
} else {
131+
option::none
132+
}
133+
}
134+
135+
fn str_mode(str s) -> mode {
136+
alt s {
137+
"compile-fail" { mode_compile_fail }
138+
"run-fail" { mode_run_fail }
139+
"run-pass" { mode_run_pass }
140+
_ { fail "invalid mode" }
141+
}
142+
}
143+
134144
fn mode_str(mode mode) -> str {
135145
alt (mode) {
136146
mode_compile_fail { "compile-fail" }
@@ -147,7 +157,7 @@ fn run_tests(&config config) {
147157
auto cx = rec(config = config,
148158
procsrv = procsrv::mk());
149159
auto tests = make_tests(cx);
150-
test::run_tests_console(opts, tests);
160+
test::run_tests_console_(opts, tests.tests, tests.to_task);
151161
procsrv::close(cx.procsrv);
152162
}
153163

@@ -156,16 +166,21 @@ fn test_opts(&config config) -> test::test_opts {
156166
run_ignored = config.run_ignored)
157167
}
158168

159-
fn make_tests(&cx cx) -> test::test_desc[] {
169+
type tests_and_conv_fn = rec(test::test_desc[] tests,
170+
fn(&fn()) -> task to_task);
171+
172+
fn make_tests(&cx cx) -> tests_and_conv_fn {
160173
log #fmt("making tests from %s", cx.config.src_base);
174+
auto configport = port[str]();
161175
auto tests = ~[];
162176
for (str file in fs::list_dir(cx.config.src_base)) {
163177
log #fmt("inspecting file %s", file);
164178
if (is_test(file)) {
165-
tests += ~[make_test(cx, file)];
179+
tests += ~[make_test(cx, file, configport)];
166180
}
167181
}
168-
ret tests;
182+
ret rec(tests = tests,
183+
to_task = bind closure_to_task(cx, configport, _));
169184
}
170185

171186
fn is_test(&str testfile) -> bool {
@@ -176,9 +191,10 @@ fn is_test(&str testfile) -> bool {
176191
|| str::starts_with(name, "~"))
177192
}
178193

179-
fn make_test(&cx cx, &str testfile) -> test::test_desc {
194+
fn make_test(&cx cx, &str testfile,
195+
&port[str] configport) -> test::test_desc {
180196
rec(name = testfile,
181-
fn = make_test_fn(cx, testfile),
197+
fn = make_test_closure(testfile, chan(configport)),
182198
ignore = is_test_ignored(cx.config, testfile))
183199
}
184200

@@ -206,44 +222,97 @@ iter iter_header(&str testfile) -> str {
206222
}
207223
}
208224

209-
fn make_test_fn(&cx cx, &str testfile) -> test::test_fn {
210-
// We're doing some ferociously unsafe nonsense here by creating a closure
211-
// and letting the test runner spawn it into a task. To avoid having
212-
// different tasks fighting over their refcounts and then the wrong task
213-
// freeing a box we need to clone everything, and make sure our closure
214-
// outlives all the tasks.
215-
fn clonestr(&str s) -> str {
216-
str::unsafe_from_bytes(str::bytes(s))
217-
}
225+
/*
226+
So this is kind of crappy:
227+
228+
A test is just defined as a function, as you might expect, but tests have to
229+
run their own tasks. Unfortunately, if your test needs dynamic data then it
230+
needs to be a closure, and transferring closures across tasks without
231+
committing a host of memory management transgressions is just impossible.
232+
233+
To get around this, the standard test runner allows you the opportunity do
234+
your own conversion from a test function to a task. It gives you your function
235+
and you give it back a task.
236+
237+
So that's what we're going to do. Here's where it gets stupid. To get the
238+
the data out of the test function we are going to run the test function,
239+
which will do nothing but send the data for that test to a port we've set
240+
up. Then we'll spawn that data into another task and return the task.
241+
Really convoluted. Need to think up of a better definition for tests.
242+
*/
243+
244+
fn make_test_closure(&str testfile,
245+
chan[str] configchan) -> test::test_fn {
246+
bind send_config(testfile, configchan)
247+
}
248+
249+
fn send_config(str testfile, chan[str] configchan) {
250+
task::send(configchan, testfile);
251+
}
252+
253+
/*
254+
FIXME: Good god forgive me.
255+
256+
So actually shuttling structural data across tasks isn't possible at this
257+
time, but we can send strings! Sadly, I need the whole config record, in the
258+
test task so, instead of fixing the mechanism in the compiler I'm going to
259+
break up the config record and pass everything individually to the spawned
260+
function. */
261+
262+
fn closure_to_task(cx cx, port[str] configport, &fn() testfn) -> task{
263+
testfn();
264+
auto testfile = task::recv(configport);
265+
ret spawn run_test_task(cx.config.compile_lib_path,
266+
cx.config.run_lib_path,
267+
cx.config.rustc_path,
268+
cx.config.src_base,
269+
cx.config.build_base,
270+
cx.config.stage_id,
271+
mode_str(cx.config.mode),
272+
cx.config.run_ignored,
273+
opt_str(cx.config.filter),
274+
opt_str(cx.config.runtool),
275+
opt_str(cx.config.rustcflags),
276+
cx.config.verbose,
277+
procsrv::clone(cx.procsrv).chan,
278+
testfile);
279+
}
280+
281+
fn run_test_task(str compile_lib_path,
282+
str run_lib_path,
283+
str rustc_path,
284+
str src_base,
285+
str build_base,
286+
str stage_id,
287+
str mode,
288+
bool run_ignored,
289+
str opt_filter,
290+
str opt_runtool,
291+
str opt_rustcflags,
292+
bool verbose,
293+
procsrv::reqchan procsrv_chan,
294+
str testfile) {
295+
296+
auto config = rec(compile_lib_path = compile_lib_path,
297+
run_lib_path = run_lib_path,
298+
rustc_path = rustc_path,
299+
src_base = src_base,
300+
build_base = build_base,
301+
stage_id = stage_id,
302+
mode = str_mode(mode),
303+
run_ignored = run_ignored,
304+
filter = str_opt(opt_filter),
305+
runtool = str_opt(opt_runtool),
306+
rustcflags = str_opt(opt_rustcflags),
307+
verbose = verbose);
308+
309+
auto procsrv = procsrv::from_chan(procsrv_chan);
218310

219-
fn cloneoptstr(&option::t[str] s) -> option::t[str] {
220-
alt s {
221-
option::some(?s) { option::some(clonestr(s)) }
222-
option::none { option::none }
223-
}
224-
}
311+
auto cx = rec(config = config,
312+
procsrv = procsrv);
225313

226-
auto configclone = rec(
227-
compile_lib_path = clonestr(cx.config.compile_lib_path),
228-
run_lib_path = clonestr(cx.config.run_lib_path),
229-
rustc_path = clonestr(cx.config.rustc_path),
230-
src_base = clonestr(cx.config.src_base),
231-
build_base = clonestr(cx.config.build_base),
232-
stage_id = clonestr(cx.config.stage_id),
233-
mode = cx.config.mode,
234-
run_ignored = cx.config.run_ignored,
235-
filter = cloneoptstr(cx.config.filter),
236-
runtool = cloneoptstr(cx.config.runtool),
237-
rustcflags = cloneoptstr(cx.config.rustcflags),
238-
verbose = cx.config.verbose);
239-
auto cxclone = rec(config = configclone,
240-
procsrv = procsrv::clone(cx.procsrv));
241-
auto testfileclone = clonestr(testfile);
242-
ret bind run_test(cxclone, testfileclone);
243-
}
244-
245-
fn run_test(cx cx, str testfile) {
246314
log #fmt("running %s", testfile);
315+
task::unsupervise();
247316
auto props = load_props(testfile);
248317
alt (cx.config.mode) {
249318
mode_compile_fail {
@@ -561,12 +630,16 @@ mod procsrv {
561630

562631
export handle;
563632
export mk;
633+
export from_chan;
564634
export clone;
565635
export run;
566636
export close;
637+
export reqchan;
638+
639+
type reqchan = chan[request];
567640

568641
type handle = rec(option::t[task] task,
569-
chan[request] chan);
642+
reqchan chan);
570643

571644
tag request {
572645
exec(str, str, vec[str], chan[response]);
@@ -581,6 +654,11 @@ mod procsrv {
581654
chan = res.chan);
582655
}
583656

657+
fn from_chan(&reqchan ch) -> handle {
658+
rec(task = option::none,
659+
chan = ch)
660+
}
661+
584662
fn clone(&handle handle) -> handle {
585663
// Sharing tasks across tasks appears to be (yet another) recipe for
586664
// disaster, so our handle clones will not get the task pointer.

trunk/src/test/stdtest/test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn do_not_run_ignored_tests() {
1515
fn = f,
1616
ignore = true);
1717

18-
test::run_test(desc);
18+
test::run_test(desc, test::default_test_to_task);
1919

2020
assert ran == false;
2121
}
@@ -26,7 +26,7 @@ fn ignored_tests_result_in_ignored() {
2626
auto desc = rec(name = "whatever",
2727
fn = f,
2828
ignore = true);
29-
auto res = test::run_test(desc).wait();
29+
auto res = test::run_test(desc, test::default_test_to_task).wait();
3030
assert res == test::tr_ignored;
3131
}
3232

0 commit comments

Comments
 (0)