Merge branch 'js/unit-test-suite-runner'

The "test-tool" has been taught to run testsuite tests in parallel,
bypassing the need to use the "prove" tool.

* js/unit-test-suite-runner:
  cmake: let `test-tool` run the unit tests, too
  ci: use test-tool as unit test runner on Windows
  t/Makefile: run unit tests alongside shell tests
  unit tests: add rule for running with test-tool
  test-tool run-command testsuite: support unit tests
  test-tool run-command testsuite: remove hardcoded filter
  test-tool run-command testsuite: get shell from env
  t0080: turn t-basic unit test into a helper
maint
Junio C Hamano 2024-05-15 09:52:52 -07:00
commit b7a1d47ba5
11 changed files with 74 additions and 30 deletions

View File

@ -794,6 +794,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o
TEST_BUILTINS_OBJS += test-dump-untracked-cache.o TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-env-helper.o TEST_BUILTINS_OBJS += test-env-helper.o
TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-example-decorate.o
TEST_BUILTINS_OBJS += test-example-tap.o
TEST_BUILTINS_OBJS += test-find-pack.o TEST_BUILTINS_OBJS += test-find-pack.o
TEST_BUILTINS_OBJS += test-fsmonitor-client.o TEST_BUILTINS_OBJS += test-fsmonitor-client.o
TEST_BUILTINS_OBJS += test-genrandom.o TEST_BUILTINS_OBJS += test-genrandom.o
@ -1333,7 +1334,6 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += sha1dc/%


UNIT_TEST_PROGRAMS += t-basic
UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-mem-pool
UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-ctype
@ -3235,7 +3235,7 @@ perf: all


.PRECIOUS: $(TEST_OBJS) .PRECIOUS: $(TEST_OBJS)


t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o


t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
@ -3888,5 +3888,5 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_DIR)/


.PHONY: build-unit-tests unit-tests .PHONY: build-unit-tests unit-tests
build-unit-tests: $(UNIT_TEST_PROGS) build-unit-tests: $(UNIT_TEST_PROGS)
unit-tests: $(UNIT_TEST_PROGS) unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests $(MAKE) -C t/ unit-tests

View File

@ -53,8 +53,6 @@ if test -n "$run_tests"
then then
group "Run tests" make test || group "Run tests" make test ||
handle_failed_tests handle_failed_tests
group "Run unit tests" \
make DEFAULT_UNIT_TEST_TARGET=unit-tests-prove unit-tests
fi fi
check_unignored_build_artifacts check_unignored_build_artifacts



View File

@ -17,7 +17,7 @@ handle_failed_tests


# We only have one unit test at the moment, so run it in the first slice # We only have one unit test at the moment, so run it in the first slice
if [ "$1" == "0" ] ; then if [ "$1" == "0" ] ; then
group "Run unit tests" make --quiet -C t unit-tests-prove group "Run unit tests" make --quiet -C t unit-tests-test-tool
fi fi


check_unignored_build_artifacts check_unignored_build_artifacts

View File

@ -1005,10 +1005,11 @@ endforeach()


#test-tool #test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)


list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/") list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES}) add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})
target_link_libraries(test-tool common-main) target_link_libraries(test-tool test-lib common-main)


set_target_properties(test-fake-ssh test-tool set_target_properties(test-fake-ssh test-tool
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/helper) PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/helper)

View File

@ -48,7 +48,8 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
UNIT_TESTS = $(sort $(filter-out unit-tests/bin/t-basic%,$(UNIT_TEST_PROGRAMS))) UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))


# `test-chainlint` (which is a dependency of `test-lint`, `test` and `prove`) # `test-chainlint` (which is a dependency of `test-lint`, `test` and `prove`)
# checks all tests in all scripts via a single invocation, so tell individual # checks all tests in all scripts via a single invocation, so tell individual
@ -67,7 +68,7 @@ failed:
test -z "$$failed" || $(MAKE) $$failed test -z "$$failed" || $(MAKE) $$failed


prove: pre-clean check-chainlint $(TEST_LINT) prove: pre-clean check-chainlint $(TEST_LINT)
@echo "*** prove ***"; $(CHAINLINTSUPPRESS) $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
$(MAKE) clean-except-prove-cache $(MAKE) clean-except-prove-cache


$(T): $(T):
@ -76,7 +77,7 @@ $(T):
$(UNIT_TESTS): $(UNIT_TESTS):
@echo "*** $@ ***"; $@ @echo "*** $@ ***"; $@


.PHONY: unit-tests unit-tests-raw unit-tests-prove .PHONY: unit-tests unit-tests-raw unit-tests-prove unit-tests-test-tool
unit-tests: $(DEFAULT_UNIT_TEST_TARGET) unit-tests: $(DEFAULT_UNIT_TEST_TARGET)


unit-tests-raw: $(UNIT_TESTS) unit-tests-raw: $(UNIT_TESTS)
@ -84,6 +85,13 @@ unit-tests-raw: $(UNIT_TESTS)
unit-tests-prove: unit-tests-prove:
@echo "*** prove - unit tests ***"; $(PROVE) $(GIT_PROVE_OPTS) $(UNIT_TESTS) @echo "*** prove - unit tests ***"; $(PROVE) $(GIT_PROVE_OPTS) $(UNIT_TESTS)


unit-tests-test-tool:
@echo "*** test-tool - unit tests **"
( \
cd unit-tests/bin && \
../../helper/test-tool$X run-command testsuite $(UNIT_TESTS_NO_DIR)\
)

pre-clean: pre-clean:
$(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'



View File

@ -1,4 +1,5 @@
#include "test-lib.h" #include "test-tool.h"
#include "t/unit-tests/test-lib.h"


/* /*
* The purpose of this "unit test" is to verify a few invariants of the unit * The purpose of this "unit test" is to verify a few invariants of the unit
@ -69,7 +70,7 @@ static void t_empty(void)
; /* empty */ ; /* empty */
} }


int cmd_main(int argc, const char **argv) int cmd__example_tap(int argc, const char **argv)
{ {
test_res = TEST(check_res = check_int(1, ==, 1), "passing test"); test_res = TEST(check_res = check_int(1, ==, 1), "passing test");
TEST(t_res(1), "passing test and assertion return 1"); TEST(t_res(1), "passing test and assertion return 1");

View File

@ -65,6 +65,7 @@ struct testsuite {
struct string_list tests, failed; struct string_list tests, failed;
int next; int next;
int quiet, immediate, verbose, verbose_log, trace, write_junit_xml; int quiet, immediate, verbose, verbose_log, trace, write_junit_xml;
const char *shell_path;
}; };
#define TESTSUITE_INIT { \ #define TESTSUITE_INIT { \
.tests = STRING_LIST_INIT_DUP, \ .tests = STRING_LIST_INIT_DUP, \
@ -80,7 +81,9 @@ static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
return 0; return 0;


test = suite->tests.items[suite->next++].string; test = suite->tests.items[suite->next++].string;
strvec_pushl(&cp->args, "sh", test, NULL); if (suite->shell_path)
strvec_push(&cp->args, suite->shell_path);
strvec_push(&cp->args, test);
if (suite->quiet) if (suite->quiet)
strvec_push(&cp->args, "--quiet"); strvec_push(&cp->args, "--quiet");
if (suite->immediate) if (suite->immediate)
@ -155,6 +158,8 @@ static int testsuite(int argc, const char **argv)
.task_finished = test_finished, .task_finished = test_finished,
.data = &suite, .data = &suite,
}; };
struct strbuf progpath = STRBUF_INIT;
size_t path_prefix_len;


argc = parse_options(argc, argv, NULL, options, argc = parse_options(argc, argv, NULL, options,
testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION); testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
@ -162,26 +167,36 @@ static int testsuite(int argc, const char **argv)
if (max_jobs <= 0) if (max_jobs <= 0)
max_jobs = online_cpus(); max_jobs = online_cpus();


/*
* If we run without a shell, execute the programs directly from CWD.
*/
suite.shell_path = getenv("TEST_SHELL_PATH");
if (!suite.shell_path)
strbuf_addstr(&progpath, "./");
path_prefix_len = progpath.len;

dir = opendir("."); dir = opendir(".");
if (!dir) if (!dir)
die("Could not open the current directory"); die("Could not open the current directory");
while ((d = readdir(dir))) { while ((d = readdir(dir))) {
const char *p = d->d_name; const char *p = d->d_name;


if (*p != 't' || !isdigit(p[1]) || !isdigit(p[2]) || if (!strcmp(p, ".") || !strcmp(p, ".."))
!isdigit(p[3]) || !isdigit(p[4]) || p[5] != '-' ||
!ends_with(p, ".sh"))
continue; continue;


/* No pattern: match all */ /* No pattern: match all */
if (!argc) { if (!argc) {
string_list_append(&suite.tests, p); strbuf_setlen(&progpath, path_prefix_len);
strbuf_addstr(&progpath, p);
string_list_append(&suite.tests, progpath.buf);
continue; continue;
} }


for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
if (!wildmatch(argv[i], p, 0)) { if (!wildmatch(argv[i], p, 0)) {
string_list_append(&suite.tests, p); strbuf_setlen(&progpath, path_prefix_len);
strbuf_addstr(&progpath, p);
string_list_append(&suite.tests, progpath.buf);
break; break;
} }
} }
@ -208,6 +223,7 @@ static int testsuite(int argc, const char **argv)


string_list_clear(&suite.tests, 0); string_list_clear(&suite.tests, 0);
string_list_clear(&suite.failed, 0); string_list_clear(&suite.failed, 0);
strbuf_release(&progpath);


return ret; return ret;
} }

View File

@ -30,6 +30,7 @@ static struct test_cmd cmds[] = {
{ "dump-untracked-cache", cmd__dump_untracked_cache }, { "dump-untracked-cache", cmd__dump_untracked_cache },
{ "env-helper", cmd__env_helper }, { "env-helper", cmd__env_helper },
{ "example-decorate", cmd__example_decorate }, { "example-decorate", cmd__example_decorate },
{ "example-tap", cmd__example_tap },
{ "find-pack", cmd__find_pack }, { "find-pack", cmd__find_pack },
{ "fsmonitor-client", cmd__fsmonitor_client }, { "fsmonitor-client", cmd__fsmonitor_client },
{ "genrandom", cmd__genrandom }, { "genrandom", cmd__genrandom },

View File

@ -24,6 +24,7 @@ int cmd__dump_untracked_cache(int argc, const char **argv);
int cmd__dump_reftable(int argc, const char **argv); int cmd__dump_reftable(int argc, const char **argv);
int cmd__env_helper(int argc, const char **argv); int cmd__env_helper(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv); int cmd__example_decorate(int argc, const char **argv);
int cmd__example_tap(int argc, const char **argv);
int cmd__find_pack(int argc, const char **argv); int cmd__find_pack(int argc, const char **argv);
int cmd__fsmonitor_client(int argc, const char **argv); int cmd__fsmonitor_client(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv); int cmd__genrandom(int argc, const char **argv);

18
t/run-test.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh

# A simple wrapper to run shell tests via TEST_SHELL_PATH,
# or exec unit tests directly.

case "$1" in
*.sh)
if test -z "${TEST_SHELL_PATH}"
then
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
exec "${TEST_SHELL_PATH}" "$@"
;;
*)
exec "$@"
;;
esac

View File

@ -9,50 +9,50 @@ test_expect_success 'TAP output from unit tests' '
cat >expect <<-EOF && cat >expect <<-EOF &&
ok 1 - passing test ok 1 - passing test
ok 2 - passing test and assertion return 1 ok 2 - passing test and assertion return 1
# check "1 == 2" failed at t/unit-tests/t-basic.c:76 # check "1 == 2" failed at t/helper/test-example-tap.c:77
# left: 1 # left: 1
# right: 2 # right: 2
not ok 3 - failing test not ok 3 - failing test
ok 4 - failing test and assertion return 0 ok 4 - failing test and assertion return 0
not ok 5 - passing TEST_TODO() # TODO not ok 5 - passing TEST_TODO() # TODO
ok 6 - passing TEST_TODO() returns 1 ok 6 - passing TEST_TODO() returns 1
# todo check ${SQ}check(x)${SQ} succeeded at t/unit-tests/t-basic.c:25 # todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26
not ok 7 - failing TEST_TODO() not ok 7 - failing TEST_TODO()
ok 8 - failing TEST_TODO() returns 0 ok 8 - failing TEST_TODO() returns 0
# check "0" failed at t/unit-tests/t-basic.c:30 # check "0" failed at t/helper/test-example-tap.c:31
# skipping test - missing prerequisite # skipping test - missing prerequisite
# skipping check ${SQ}1${SQ} at t/unit-tests/t-basic.c:32 # skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33
ok 9 - test_skip() # SKIP ok 9 - test_skip() # SKIP
ok 10 - skipped test returns 1 ok 10 - skipped test returns 1
# skipping test - missing prerequisite # skipping test - missing prerequisite
ok 11 - test_skip() inside TEST_TODO() # SKIP ok 11 - test_skip() inside TEST_TODO() # SKIP
ok 12 - test_skip() inside TEST_TODO() returns 1 ok 12 - test_skip() inside TEST_TODO() returns 1
# check "0" failed at t/unit-tests/t-basic.c:48 # check "0" failed at t/helper/test-example-tap.c:49
not ok 13 - TEST_TODO() after failing check not ok 13 - TEST_TODO() after failing check
ok 14 - TEST_TODO() after failing check returns 0 ok 14 - TEST_TODO() after failing check returns 0
# check "0" failed at t/unit-tests/t-basic.c:56 # check "0" failed at t/helper/test-example-tap.c:57
not ok 15 - failing check after TEST_TODO() not ok 15 - failing check after TEST_TODO()
ok 16 - failing check after TEST_TODO() returns 0 ok 16 - failing check after TEST_TODO() returns 0
# check "!strcmp("\thello\\\\", "there\"\n")" failed at t/unit-tests/t-basic.c:61 # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:62
# left: "\011hello\\\\" # left: "\011hello\\\\"
# right: "there\"\012" # right: "there\"\012"
# check "!strcmp("NULL", NULL)" failed at t/unit-tests/t-basic.c:62 # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:63
# left: "NULL" # left: "NULL"
# right: NULL # right: NULL
# check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/unit-tests/t-basic.c:63 # check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/helper/test-example-tap.c:64
# left: ${SQ}a${SQ} # left: ${SQ}a${SQ}
# right: ${SQ}\012${SQ} # right: ${SQ}\012${SQ}
# check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/unit-tests/t-basic.c:64 # check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/helper/test-example-tap.c:65
# left: ${SQ}\\\\${SQ} # left: ${SQ}\\\\${SQ}
# right: ${SQ}\\${SQ}${SQ} # right: ${SQ}\\${SQ}${SQ}
not ok 17 - messages from failing string and char comparison not ok 17 - messages from failing string and char comparison
# BUG: test has no checks at t/unit-tests/t-basic.c:91 # BUG: test has no checks at t/helper/test-example-tap.c:92
not ok 18 - test with no checks not ok 18 - test with no checks
ok 19 - test with no checks returns 0 ok 19 - test with no checks returns 0
1..19 1..19
EOF EOF


! "$GIT_BUILD_DIR"/t/unit-tests/bin/t-basic >actual && ! test-tool example-tap >actual &&
test_cmp expect actual test_cmp expect actual
' '