Skip to content

Commit efd8910

Browse files
committed
Improve parallel test runs
The main improvement is to separate the per-app makefile targets early, during templating phase, so they never share a common app structure during runtime. That lets us avoid reconfiguring the data directory at runtime, which effectively is an almost full restart internally since we're tearing down all the couch servers. So we're saving an extra app shutdown + restart cycle per app with the new scheme and improving the isolation. The new scheme uses the `tmp/$app` namespace for each app. After the tests run any unsuccessful `tmp/$app` directories will be left behind so they can be inspected. This is especially useful for looking at the failed couch.log entries. There is a separate catlogs target to concatenate failed app couch.log files though maybe keeping them separately may be even easier to inspect. Since if app a failed, we'd just rather look at app a's logs and not app c's logs. Since setup_eunit rebar template now takes an app argument, opted to remove some older eunit targets keeping only the new ones supporting parallel execution. Otherwise, would have to add a special case statement in the plugin for no-app vs one app cases. Previously, `make eunit apps=a,b,c` did not run apps `a`, `b` and `c` as separate goals in parallel execution. They were passed in as the `a,b,c` goal. To fix that parse the apps into individual space separated goals, so they can be sped up by parallel execution as well. To keep the apps as isolated from each other as possible make build_eunit_config also create separate etc folders, use separate BUILDDIR macros and each apps only uses its own fixtures. Also use 0-valued ports the eunit template to avoid any chance of port collision and resulting flakiness. Rename SUBDIRS to EUNIT_SUBDIRS since they used for eunit tests currently only. Also, exclude docs and fauxton apps from the target list, since they are not proper erlang apps and don't have eunit tests. Newer Erlangs have `file:del_dir_r/1` so use that in cleanup_dirs to save a few lines of code.
1 parent 75623a4 commit efd8910

9 files changed

Lines changed: 85 additions & 127 deletions

File tree

Makefile

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -168,53 +168,27 @@ check: all
168168
@$(MAKE) nouveau-test
169169

170170
ifdef apps
171-
SUBDIRS = $(apps)
171+
EUNIT_SUBDIRS = $(strip $(subst $(comma),$(space),$(apps)))
172172
else
173-
SUBDIRS=$(shell ls src)
173+
EUNIT_SUBDIRS = $(filter-out fauxton docs, $(shell ls src))
174174
endif
175175

176-
# Used for comparing behaviour against he new `eunit` target, delete once we
177-
# are happy with the new `eunit`.
178-
.PHONY: old-eunit
179-
old-eunit: export BUILDDIR = $(CURDIR)
180-
old-eunit: export ERL_AFLAGS = -config $(CURDIR)/rel/files/eunit.config
181-
old-eunit: export COUCHDB_QUERY_SERVER_JAVASCRIPT = $(CURDIR)/bin/couchjs $(CURDIR)/share/server/main.js
182-
old-eunit: export COUCHDB_TEST_ADMIN_PARTY_OVERRIDE=1
183-
old-eunit:
184-
@COUCHDB_VERSION=$(COUCHDB_VERSION) COUCHDB_GIT_SHA=$(COUCHDB_GIT_SHA) $(REBAR) setup_eunit 2> /dev/null
185-
@for dir in $(SUBDIRS); do \
186-
COUCHDB_VERSION=$(COUCHDB_VERSION) COUCHDB_GIT_SHA=$(COUCHDB_GIT_SHA) $(REBAR) -r eunit $(EUNIT_OPTS) apps=$$dir || exit 1; \
187-
done
188-
189176
# target: eunit - Run EUnit tests, use EUNIT_OPTS to provide custom options
190-
.PHONY: eunit $(SUBDIRS)
191-
eunit: export BUILDDIR = $(CURDIR)
177+
.PHONY: eunit $(EUNIT_SUBDIRS)
192178
eunit: export ERL_AFLAGS = -config $(CURDIR)/rel/files/eunit.config
193179
eunit: export COUCHDB_QUERY_SERVER_JAVASCRIPT = $(CURDIR)/bin/couchjs $(CURDIR)/share/server/main.js
194180
eunit: export COUCHDB_TEST_ADMIN_PARTY_OVERRIDE=1
195-
eunit: ${SUBDIRS}
196-
@cat tmp/couchdb-tests/*/log/couch.log > tmp/couch.log
197-
@rm -rf tmp/couchdb-tests/*
198-
199-
$(SUBDIRS): setup-eunit
200-
@COUCHDB_VERSION=$(COUCHDB_VERSION) COUCHDB_GIT_SHA=$(COUCHDB_GIT_SHA) $(REBAR) -r eunit $(EUNIT_OPTS) apps=$@ #|| exit 1
201-
202-
setup-eunit: export BUILDDIR = $(CURDIR)
203-
setup-eunit: export ERL_AFLAGS = -config $(CURDIR)/rel/files/eunit.config
204-
setup-eunit:
205-
@$(REBAR) setup_eunit 2> /dev/null
206-
207-
just-eunit: export BUILDDIR = $(CURDIR)
208-
just-eunit: export ERL_AFLAGS = -config $(CURDIR)/rel/files/eunit.config
209-
just-eunit:
210-
@$(REBAR) -r eunit $(EUNIT_OPTS)
211-
212-
.PHONY: soak-eunit
213-
soak-eunit: export BUILDDIR = $(CURDIR)
214-
soak-eunit: export ERL_AFLAGS = -config $(CURDIR)/rel/files/eunit.config
215-
soak-eunit: couch-core
216-
@$(REBAR) setup_eunit 2> /dev/null
217-
while [ $$? -eq 0 ] ; do $(REBAR) -r eunit $(EUNIT_OPTS) ; done
181+
eunit: ${EUNIT_SUBDIRS}
182+
183+
$(EUNIT_SUBDIRS):
184+
@rm -rf tmp/$@
185+
@$(REBAR) setup_eunit app=$@ >/dev/null
186+
@BUILDDIR=$(CURDIR)/tmp/$@ COUCHDB_VERSION=$(COUCHDB_VERSION) COUCHDB_GIT_SHA=$(COUCHDB_GIT_SHA) $(REBAR) -r eunit $(EUNIT_OPTS) apps=$@ && rm -rf tmp/$@
187+
188+
# cat together couch_log files after running eunit test
189+
.PHONY: catlogs
190+
catlogs:
191+
@ls tmp/*/couch.log 2>/dev/null | xargs cat > tmp/couch.log || true
218192

219193
# target: erlfmt-check - Check Erlang source code formatting
220194
erlfmt-check:
@@ -316,6 +290,7 @@ build-report:
316290
build-aux/show-test-results.py --suites=10 --tests=10 > test-results.log || true
317291
cat ./dev/logs/node1.log || true
318292
cat ./dev/logs/nouveau.log || true
293+
$(MAKE) catlogs || true
319294
cat ./tmp/couch.log || true
320295
cat test-results.log || true
321296

rel/plugins/eunit_plugin.erl

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,56 @@ is_base_dir(RebarConf) ->
2525
filename:absname(rebar_utils:get_cwd()) =:=
2626
rebar_config:get_xconf(RebarConf, base_dir, undefined).
2727

28-
build_eunit_config(Config0, AppFile) ->
28+
build_eunit_config(Config, AppFile) ->
2929
Cwd = filename:absname(rebar_utils:get_cwd()),
30-
DataDir = Cwd ++ "/tmp/data",
31-
ViewIndexDir = Cwd ++ "/tmp/data",
32-
StateDir = Cwd ++ "/tmp/data",
33-
TmpDataDir = Cwd ++ "/tmp/tmp_data",
34-
LogDir = Cwd ++ "/tmp",
35-
cleanup_dirs([DataDir, TmpDataDir]),
36-
Config1 = rebar_config:set_global(Config0, template, "setup_eunit"),
37-
Config2 = rebar_config:set_global(Config1, prefix, Cwd),
30+
App = rebar_config:get_global(Config, app, undefined),
31+
case is_list(App) of
32+
true -> ok;
33+
false -> error(app_parameter_must_be_defined)
34+
end,
35+
case re:run(App, "^[_a-z0-9]+$") of
36+
nomatch ->
37+
error({app_parameter_must_be_just_one_app, App});
38+
{match, _} ->
39+
Prefix = Cwd ++ "/tmp/" ++ App,
40+
DataDir = Prefix ++ "/data",
41+
ViewIndexDir = Prefix ++ "/data",
42+
StateDir = Prefix ++ "/data",
43+
TmpDataDir = Prefix ++ "/tmp_data",
44+
EtcDir = Prefix ++ "/etc",
45+
LogDir = Prefix,
46+
build_config(
47+
Config,
48+
AppFile,
49+
Prefix,
50+
DataDir,
51+
ViewIndexDir,
52+
StateDir,
53+
TmpDataDir,
54+
EtcDir,
55+
LogDir
56+
)
57+
end.
58+
59+
build_config(Config, AppFile, Prefix, DataDir, ViewIndexDir, StateDir, TmpDataDir, EtcDir, LogDir) ->
60+
cleanup_dirs([DataDir, TmpDataDir, EtcDir]),
61+
Config1 = rebar_config:set_global(Config, template, "setup_eunit"),
62+
Config2 = rebar_config:set_global(Config1, prefix, Prefix),
3863
Config3 = rebar_config:set_global(Config2, data_dir, DataDir),
3964
Config4 = rebar_config:set_global(Config3, view_index_dir, ViewIndexDir),
4065
Config5 = rebar_config:set_global(Config4, log_dir, LogDir),
41-
Config = rebar_config:set_global(Config5, state_dir, StateDir),
42-
rebar_templater:create(Config, AppFile).
66+
Config6 = rebar_config:set_global(Config5, etc_dir, EtcDir),
67+
Config7 = rebar_config:set_global(Config6, tmp_data, TmpDataDir),
68+
Config8 = rebar_config:set_global(Config7, state_dir, StateDir),
69+
rebar_templater:create(Config8, AppFile).
4370

4471
cleanup_dirs(Dirs) ->
4572
lists:foreach(
4673
fun(Dir) ->
4774
case filelib:is_dir(Dir) of
48-
true -> del_dir(Dir);
75+
true -> file:del_dir_r(Dir);
4976
false -> ok
5077
end
5178
end,
5279
Dirs
5380
).
54-
55-
del_dir(Dir) ->
56-
All = filelib:wildcard(Dir ++ "/**"),
57-
{Dirs, Files} = lists:partition(fun filelib:is_dir/1, All),
58-
ok = lists:foreach(fun file:delete/1, Files),
59-
SortedDirs = lists:sort(fun(A, B) -> length(A) > length(B) end, Dirs),
60-
ok = lists:foreach(fun file:del_dir/1, SortedDirs),
61-
ok = file:del_dir(Dir).

setup_eunit.template

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
{variables, [
22
{package_author_name, "The Apache Software Foundation"},
3-
{cluster_port, 5984},
4-
{backend_port, 5986},
5-
{prometheus_port, 17986},
3+
{cluster_port, 0},
4+
{backend_port, 0},
5+
{prometheus_port, 0},
66
{node_name, "-name couchdbtest@127.0.0.1"},
77

88
{data_dir, "/tmp"},
99
{prefix, "/tmp"},
1010
{view_index_dir, "/tmp"},
1111
{state_dir, "/tmp"},
12-
{log_dir, "/tmp"}
12+
{log_dir, "/tmp"},
13+
{etc_dir, "/tmp"},
14+
{tmp_data, "/tmp"}
1315
]}.
14-
{dir, "tmp"}.
15-
{dir, "tmp/etc"}.
16-
{dir, "tmp/data"}.
17-
{dir, "tmp/tmp_data"}.
18-
{template, "rel/overlay/etc/default.ini", "tmp/etc/default_eunit.ini"}.
19-
{template, "rel/overlay/etc/local.ini", "tmp/etc/local_eunit.ini"}.
20-
{template, "rel/files/eunit.ini", "tmp/etc/eunit.ini"}.
21-
{template, "rel/overlay/etc/vm.args", "tmp/etc/vm.args"}.
16+
{dir, "{{prefix}}"}.
17+
{dir, "{{etc_dir}}"}.
18+
{dir, "{{data_dir}}"}.
19+
{dir, "{{tmp_data}}"}.
20+
21+
{template, "rel/overlay/etc/default.ini", "{{prefix}}/etc/default_eunit.ini"}.
22+
{template, "rel/overlay/etc/local.ini", "{{prefix}}/etc/local_eunit.ini"}.
23+
{template, "rel/files/eunit.ini", "{{prefix}}/etc/eunit.ini"}.
24+
{template, "rel/overlay/etc/vm.args", "{{prefix}}/etc/vm.args"}.

src/config/test/config_tests.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
-define(RESTART_TIMEOUT_IN_MILLISEC, 3000).
2525

2626
-define(CONFIG_FIXTURESDIR,
27-
filename:join([?BUILDDIR(), "src", "config", "test", "fixtures"])
27+
?ABS_PATH("test/fixtures")
2828
).
2929

3030
-define(CONFIG_DEFAULT_TEST,

src/couch/include/couch_eunit.hrl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,23 @@
2525
end
2626
end).
2727
-define(CONFIG_DEFAULT,
28-
filename:join([?BUILDDIR(), "tmp", "etc", "default_eunit.ini"])).
28+
filename:join([?BUILDDIR(), "etc", "default_eunit.ini"])).
2929
-define(CONFIG_CHAIN, [
3030
?CONFIG_DEFAULT,
31-
filename:join([?BUILDDIR(), "tmp", "etc", "local_eunit.ini"]),
32-
filename:join([?BUILDDIR(), "tmp", "etc", "eunit.ini"])]).
33-
-define(FIXTURESDIR,
34-
filename:join([?BUILDDIR(), "src", "couch", "test", "eunit", "fixtures"])).
31+
filename:join([?BUILDDIR(), "etc", "local_eunit.ini"]),
32+
filename:join([?BUILDDIR(), "etc", "eunit.ini"])]).
3533
-define(TEMPDIR,
36-
filename:join([?BUILDDIR(), "tmp", "tmp_data"])).
34+
filename:join([?BUILDDIR(), "tmp_data"])).
3735

3836
-define(APPDIR, filename:dirname(element(2, file:get_cwd()))).
3937
%% Account for the fact that source files are in src/<app>/.eunit/<module>.erl
4038
%% when run from eunit
4139
-define(ABS_PATH(File), %% src/<app>/.eunit/<module>.erl
4240
filename:join([?APPDIR, File])).
4341

42+
-define(FIXTURESDIR,
43+
?ABS_PATH("test/eunit/fixtures")).
44+
4445
-define(tempfile,
4546
fun() ->
4647
Suffix = couch_uuids:random(),

src/couch/src/test_util.erl

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
-include("couch_db_int.hrl").
4545
-include("couch_bt_engine.hrl").
4646

47-
-record(test_context, {mocked = [], started = [], module, dir}).
47+
-record(test_context, {mocked = [], started = [], module}).
4848

4949
-define(DEFAULT_APPS, [inets, ibrowse, ssl, config, couch_epi, couch_event, couch]).
5050

@@ -81,61 +81,21 @@ start_couch() ->
8181
start_couch(ExtraApps) ->
8282
start_couch(?CONFIG_CHAIN, ExtraApps).
8383

84-
% This function starts CouchDB with optional extra apps in a dedicated
85-
% directory under tmp/couchdb-tests/<uuid>/ — That is, all instances
86-
% run isolated so that if more than one runs at a time, it does not
87-
% interfere with any of the others.
88-
% The sub-directories here are etc/ log/ and data/ for configuration
89-
% logs and databases and view indexes respectively.
90-
% The function copies the initial bespoke test-ini files to the new
91-
% random directory and configures CouchDB to use those.
92-
% The two pieces of state created here are the random dir in the file system and
93-
% the process env var {config, ini_files} that is read by couch_config on
94-
% startup. These need to be reset at the right points in time.
95-
% the random dir is deleted and the env var reset when CouchDB is stopped.
96-
% Note: there is currently a case where stop_couch/0 could be called and
97-
% the file system cleanup can’t be run because we don’t get passed the test
98-
% context with the random directory value. At the moment stop_couch/0 is not
99-
% used anywhere, tho. Maybe we should remove it?
10084
start_couch(IniFiles, ExtraApps) ->
101-
RandomDir = filename:join([builddir(), "tmp", "couchdb-tests", couch_uuids:random()]),
102-
RandomEtcDir = filename:join([RandomDir, "etc"]),
103-
RandomDataDir = ?b2l(filename:join([RandomDir, "data"])),
104-
RandomLogFile = ?b2l(filename:join([RandomDir, "log", "couch.log"])),
105-
106-
ok = filelib:ensure_path(RandomDir),
107-
ok = filelib:ensure_path(RandomEtcDir),
108-
ok = filelib:ensure_path(RandomDataDir),
109-
ok = filelib:ensure_dir(RandomLogFile),
110-
111-
RandomIniFiles = lists:map(
112-
fun(SourceFile) ->
113-
TargetFileName = lists:last(filename:split(SourceFile)),
114-
TargetFile = filename:join([RandomEtcDir, TargetFileName]),
115-
{ok, _} = file:copy(SourceFile, TargetFile),
116-
?b2l(TargetFile)
117-
end,
118-
IniFiles
119-
),
12085
load_applications_with_stats(),
121-
ok = application:set_env(config, ini_files, RandomIniFiles),
86+
ok = application:set_env(config, ini_files, IniFiles),
12287
Apps = start_applications(?DEFAULT_APPS ++ ExtraApps),
123-
124-
ok = config:set("couchdb", "database_dir", RandomDataDir, false),
125-
ok = config:set("couchdb", "view_index_dir", RandomDataDir, false),
126-
ok = config:set("log", "file", RandomLogFile, false),
12788
ok = config:delete("compactions", "_default", false),
128-
#test_context{started = Apps, dir = RandomDir}.
89+
#test_context{started = Apps}.
12990

13091
stop_couch() ->
13192
ok = stop_applications(?DEFAULT_APPS).
13293

133-
stop_couch(#test_context{started = Apps, dir = RandomDir}) ->
134-
% deletion now happens in Makefile
135-
% file:del_dir_r(RandomDir),
94+
stop_couch(#test_context{started = Apps}) ->
13695
stop_applications(Apps);
13796
stop_couch(_) ->
13897
stop_couch().
98+
13999
with_couch_server_restart(Fun) ->
140100
Servers = couch_server:names(),
141101
test_util:with_processes_restart(Servers, Fun).
@@ -415,7 +375,7 @@ mock(couch_stats) ->
415375
ok.
416376

417377
load_applications_with_stats() ->
418-
Wildcard = filename:join([?BUILDDIR(), "src/*/priv/stats_descriptions.cfg"]),
378+
Wildcard = ?ABS_PATH("../*/priv/stats_descriptions.cfg"),
419379
[application:load(stats_file_to_app(File)) || File <- filelib:wildcard(Wildcard)],
420380
ok.
421381

2.94 KB
Loading

src/mem3/test/eunit/mem3_seeds_test.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ teardown(Ctx) ->
106106
catch application:stop(mem3),
107107
config:delete("cluster", "seedlist", false),
108108
Filename = config:get("mem3", "nodes_db", "_nodes") ++ ".couch",
109-
file:delete(filename:join([?BUILDDIR(), "tmp", "data", Filename])),
109+
file:delete(filename:join([?BUILDDIR(), "data", Filename])),
110110
case config:get("couch_httpd_auth", "authentication_db") of
111111
undefined -> ok;
112112
DbName -> couch_server:delete(list_to_binary(DbName), [])

src/mem3/test/eunit/mem3_zone_test.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ teardown(Ctx) ->
6868
catch application:stop(mem3),
6969
os:unsetenv("COUCHDB_ZONE"),
7070
Filename = config:get("mem3", "nodes_db", "_nodes") ++ ".couch",
71-
file:delete(filename:join([?BUILDDIR(), "tmp", "data", Filename])),
71+
file:delete(filename:join([?BUILDDIR(), "data", Filename])),
7272
case config:get("couch_httpd_auth", "authentication_db") of
7373
undefined -> ok;
7474
DbName -> couch_server:delete(list_to_binary(DbName), [])

0 commit comments

Comments
 (0)