From 4f841757b84ddb2cf844252633dc2403569aa066 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 29 Aug 2018 15:41:07 +0200 Subject: Add unit-tests option to bdep-new If specified (-t exe,unit-tests or -t lib,unit-tests) then generate build infrastructure for unit testing. --- bdep/new.cli | 12 +- bdep/new.cxx | 330 +++++++++++++++++++++++++++++++++++------------------ build/root.build | 5 + tests/ci.test | 2 +- tests/config.test | 2 +- tests/fetch.test | 2 +- tests/init.test | 2 +- tests/new.test | 73 +++++++++++- tests/publish.test | 2 +- tests/status.test | 2 +- tests/sync.test | 2 +- tests/test.test | 2 +- tests/update.test | 2 +- 13 files changed, 309 insertions(+), 129 deletions(-) diff --git a/bdep/new.cli b/bdep/new.cli index d7e3ed0..688f14c 100644 --- a/bdep/new.cli +++ b/bdep/new.cli @@ -76,6 +76,8 @@ namespace bdep project options: \cb{no-tests} \- Don't add support for functional/integration testing. + + \cb{unit-tests} \- Add support for unit testing. | \li|\cb{lib} @@ -84,14 +86,16 @@ namespace bdep options: \cb{no-tests} \- Don't add support for functional/integration testing. - | + + \cb{unit-tests} \- Add support for unit testing. + | \li|\cb{bare} A project without any source code. Recognized bare project options: - \cb{no-tests} \- Don't add support for functional/integration testing. - | + \cb{no-tests} \- Don't add support for testing. + | \li|\cb{empty} @@ -142,11 +146,13 @@ namespace bdep class cmd_new_exe_options { bool no-tests; + bool unit-tests; }; class cmd_new_lib_options { bool no-tests; + bool unit-tests; }; class cmd_new_bare_options diff --git a/bdep/new.cxx b/bdep/new.cxx index 45c3234..264cd47 100644 --- a/bdep/new.cxx +++ b/bdep/new.cxx @@ -58,10 +58,13 @@ namespace bdep // const type& t (o.type ()); - bool tests (t == type::exe ? !t.exe_opt.no_tests () : + bool itest (t == type::exe ? !t.exe_opt.no_tests () : t == type::lib ? !t.lib_opt.no_tests () : t == type::bare ? !t.bare_opt.no_tests () : false); + bool utest (t == type::exe ? t.exe_opt.unit_tests () : + t == type::lib ? t.lib_opt.unit_tests () : false); + // Validate language options. // const lang& l (o.lang ()); @@ -169,7 +172,6 @@ namespace bdep } } - dir_path out; // Project/package output directory. dir_path prj; // Project. optional pkg; // Package relative to its project root. @@ -403,7 +405,7 @@ namespace bdep os << endl << "using version" << endl << "using config" << endl; - if (tests) + if (itest || utest) os << "using test" << endl; os << "using install" << endl << "using dist" << endl; @@ -414,10 +416,20 @@ namespace bdep // Note: see also tests/build/root.build below. // os.open (f = bd / "root.build"); + + const char* x (nullptr); // Language module/source target type. + const char* h (nullptr); // Header target type. + const char* hs (nullptr); // All header target types. + string es; // Source file extension suffix (pp, xx). + switch (l) { case lang::c: { + x = "c"; + h = "h"; + hs = "h"; + // @@ TODO: 'latest' in c.std. // os //<< "c.std = latest" << endl @@ -430,24 +442,29 @@ namespace bdep } case lang::cxx: { - const char* s (l.cxx_opt.cpp () ? "pp" : "xx"); + x = "cxx"; + h = "hxx"; + hs = "hxx ixx txx"; + es = l.cxx_opt.cpp () ? "pp" : "xx"; os << "cxx.std = latest" << endl << endl << "using cxx" << endl << endl - << "hxx{*}: extension = h" << s << endl - << "ixx{*}: extension = i" << s << endl - << "txx{*}: extension = t" << s << endl - << "cxx{*}: extension = c" << s << endl; + << "hxx{*}: extension = h" << es << endl + << "ixx{*}: extension = i" << es << endl + << "txx{*}: extension = t" << es << endl + << "cxx{*}: extension = c" << es << endl; break; } } - if (tests) + + if ((itest || utest) && x != nullptr) os << endl << "# The test target for cross-testing (running tests under Wine, etc)." << endl << "#" << endl - << "test.target = $cxx.target" << endl; + << "test.target = $" << x << ".target" << endl; + os.close (); // build/.gitignore @@ -465,7 +482,7 @@ namespace bdep // os.open (f = out / "buildfile"); os << "./: {*/ -build/} manifest" << endl; - if (tests && t == type::lib) // Have tests/ subproject. + if (itest && t == type::lib) // Have tests/ subproject. os << endl << "# Don't install tests." << endl << "#" << endl @@ -510,11 +527,9 @@ namespace bdep } case lang::cxx: { - string x (l.cxx_opt.cpp () ? "pp" : "xx"); - // /.c(xx|pp) // - os.open (f = sd / s + ".c" + x); + os.open (f = sd / s + ".c" + es); os << "#include " << endl << endl << "int main (int argc, char* argv[])" << endl @@ -538,30 +553,36 @@ namespace bdep // /buildfile // os.open (f = sd / "buildfile"); - os << "libs =" << endl - << "#import libs += libhello%lib{hello}" << endl - << endl; - - const char* x (nullptr); // Language module. - switch (l) - { - case lang::c: - { - os << "exe{" << s << "}: {h c}{**} $libs" << - (tests ? " testscript" : "") << endl; - - x = "c"; - break; - } - case lang::cxx: - { - os << "exe{" << s << "}: {hxx ixx txx cxx}{**} $libs" << - (tests ? " testscript" : "") << endl; + os << "libs =" << endl + << "#import libs += libhello%lib{hello}" << endl + << endl; - x = "cxx"; - break; - } - } + if (!utest) + os << "exe{" << s << "}: " << + "{" << hs << ' ' << x << "}{**} " << + "$libs" << + (itest ? " testscript" : "") << endl; + else + os << "./: exe{" << s << "}" << endl + << "exe{" << s << "}: libue{" << s << "}" << + (itest ? " testscript" : "") << endl + << "libue{" << s << "}: " << + "{" << hs << ' ' << x << "}{** -**.test...} $libs" << endl + << endl + << "# Unit tests." << endl + << "#" << endl + << "exe{*.test}: test = true" << endl + << "exe{*.test}: install = false" << endl + << endl + << "for t: " << x << "{**.test...}" << endl + << "{" << endl + << " d = $directory($t)" << endl + << " n = $name($t)..." << endl + << endl + << " ./: $d/exe{$n}" << endl + << " $d/exe{$n}: $t $d/{" << hs << "}{+$n} $d/test{+$n}" << endl + << " $d/exe{$n}: libue{" << s << "}: bin.whole = false"<< endl + << "}" << endl; os << endl << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; @@ -573,30 +594,76 @@ namespace bdep { os.open (f = sd / ".gitignore"); os << s << endl; - if (tests) + if (utest) + os << "*.test" << endl; + if (itest || utest) os << endl << "# Testscript output directory (can be symlink)." << endl - << "#" << endl - << "test-" << s << endl; + << "#" << endl; + if (itest) + os << "test-" << s << endl; + if (utest) + os << "test-*.test" << endl; os.close (); } // /testscript // - if (!tests) - break; + if (itest) + { + os.open (f = sd / "testscript"); + os << ": basics" << endl + << ":" << endl + << "$* 'World' >'Hello, World!'" << endl + << endl + << ": missing-name" << endl + << ":" << endl + << "$* 2>>EOE != 0" << endl + << "error: missing name" << endl + << "EOE" << endl; + os.close (); + } - os.open (f = sd / "testscript"); - os << ": basics" << endl - << ":" << endl - << "$* 'World' >'Hello, World!'" << endl - << endl - << ": missing-name" << endl - << ":" << endl - << "$* 2>>EOE != 0" << endl - << "error: missing name" << endl - << "EOE" << endl; - os.close (); + // /.test.* + // + if (utest) + { + switch (l) + { + case lang::c: + { + // /.test.c + // + os.open (f = sd / s + ".test.c"); + os << "#include " << endl + << "#include " << endl + << endl + << "int main ()" << endl + << "{" << endl + << " return 0;" << endl + << "}" << endl; + os.close (); + + break; + } + case lang::cxx: + { + // /.test.c(xx|pp) + // + os.open (f = sd / s + ".test.c" + es); + os << "#include " << endl + << "#include " << endl + << endl + << "int main ()" << endl + << "{" << endl + << endl + << "}" << endl; + os.close (); + + break; + } + } + } break; } @@ -662,11 +729,9 @@ namespace bdep } case lang::cxx: { - string x (l.cxx_opt.cpp () ? "pp" : "xx"); - - hdr = s + ".h" + x; - exp = "export.h" + x; - ver = "version.h" + x; + hdr = s + ".h" + es; + exp = "export.h" + es; + ver = "version.h" + es; // .h(xx|pp) // @@ -691,7 +756,7 @@ namespace bdep // .c(xx|pp) // - os.open (f = sd / s + ".c" + x); + os.open (f = sd / s + ".c" + es); os << "#include <" << b << "/" << hdr << ">" << endl << endl << "#include " << endl @@ -804,30 +869,32 @@ namespace bdep << "#import imp_libs += libhello%lib{hello}" << endl << endl; - const char* x (nullptr); // Language module. - const char* h (nullptr); // Header target type. - const char* hs (nullptr); // All header target types. - switch (l) - { - case lang::c: - { - os << "lib{" << s << "}: {h c}{** -version} h{version}" << endl; - - x = "c"; - h = "h"; - hs = "h"; - break; - } - case lang::cxx: - { - os << "lib{" << s << "}: {hxx ixx txx cxx}{** -version} hxx{version} $imp_libs $int_libs" << endl; - - x = "cxx"; - h = "hxx"; - hs = "hxx ixx txx"; - break; - } - } + if (!utest) + os << "lib{" << s << "}: " << + "{" << hs << ' ' << x << "}{** -version} " << + h << "{version} $imp_libs $int_libs" << endl; + else + os << "./: lib{" << s << "}" << endl + << "lib{" << s << "}: libul{" << s << "}" << endl + << "libul{" << s << "}: " << + "{" << hs << ' ' << x << "}{** -version -**.test...} " << + h << "{version} \\" << endl + << " $imp_libs $int_libs" << endl + << endl + << "# Unit tests." << endl + << "#" << endl + << "exe{*.test}: test = true" << endl + << "exe{*.test}: install = false" << endl + << endl + << "for t: " << x << "{**.test...}" << endl + << "{" << endl + << " d = $directory($t)" << endl + << " n = $name($t)..." << endl + << endl + << " ./: $d/exe{$n}" << endl + << " $d/exe{$n}: $t $d/{" << hs << "}{+$n} $d/test{+$n}" << endl + << " $d/exe{$n}: libul{" << s << "}: bin.whole = false"<< endl + << "}" << endl; os << endl << "# Include the generated version header into the distribution (so that we don't" << endl @@ -869,12 +936,64 @@ namespace bdep if (vc == vcs::git) { os.open (f = sd / ".gitignore"); - os << "# Generated version header." << endl - << "#" << endl - << ver << endl; + os << "# Generated version header." << endl + << "#" << endl + << ver << endl; + if (utest) + os << endl + << "# Unit test executables and Testscript output directories" << endl + << "# (can be symlinks)." << endl + << "#" << endl + << "*.test" << endl + << "test-*.test" << endl; os.close (); } + // /.test.* + // + if (utest) + { + switch (l) + { + case lang::c: + { + // /.test.c + // + os.open (f = sd / s + ".test.c"); + os << "#include " << endl + << "#include " << endl + << endl + << "#include <" << b << "/" << hdr << ">" << endl + << endl + << "int main ()" << endl + << "{" << endl + << " return 0;" << endl + << "}" << endl; + os.close (); + + break; + } + case lang::cxx: + { + // /.test.c(xx|pp) + // + os.open (f = sd / s + ".test.c" + es); + os << "#include " << endl + << "#include " << endl + << endl + << "#include <" << b << "/" << hdr << ">" << endl + << endl + << "int main ()" << endl + << "{" << endl + << endl + << "}" << endl; + os.close (); + + break; + } + } + } + // build/export.build // os.open (f = bd / "export.build"); @@ -888,7 +1007,7 @@ namespace bdep // tests/ (tests subproject). // - if (!tests) + if (!itest) break; dir_path td (dir_path (out) /= "tests"); @@ -928,16 +1047,14 @@ namespace bdep } case lang::cxx: { - const char* s (l.cxx_opt.cpp () ? "pp" : "xx"); - os << "cxx.std = latest" << endl << endl << "using cxx" << endl << endl - << "hxx{*}: extension = h" << s << endl - << "ixx{*}: extension = i" << s << endl - << "txx{*}: extension = t" << s << endl - << "cxx{*}: extension = c" << s << endl; + << "hxx{*}: extension = h" << es << endl + << "ixx{*}: extension = i" << es << endl + << "txx{*}: extension = t" << es << endl + << "cxx{*}: extension = c" << es << endl; break; } } @@ -948,7 +1065,7 @@ namespace bdep << endl << "# The test target for cross-testing (running tests under Wine, etc)." << endl << "#" << endl - << "test.target = $cxx.target" << endl; + << "test.target = $" << x << ".target" << endl; os.close (); // tests/build/.gitignore @@ -1033,11 +1150,9 @@ namespace bdep } case lang::cxx: { - string x (l.cxx_opt.cpp () ? "pp" : "xx"); - // tests/basics/driver.c(xx|pp) // - os.open (f = td / "driver.c" + x); + os.open (f = td / "driver.c" + es); os << "#include " << endl << "#include " << endl << "#include " << endl @@ -1081,23 +1196,10 @@ namespace bdep // os.open (f = td / "buildfile"); os << "import libs = " << n << "%lib{" << s << "}" << endl - << endl; - - switch (l) - { - case lang::c: - { - os << "exe{driver}: {h c}{**} $libs" << endl; - break; - } - case lang::cxx: - { - os << "exe{driver}: {hxx ixx txx cxx}{**} $libs" << endl; - break; - } - } - //os << endl - // << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; + << endl + << "exe{driver}: {" << hs << ' ' << x << "}{**} $libs test{**}" << endl; + // << endl + // << x << ".poptions =+ \"-I$out_root\" \"-I$src_root\"" << endl; os.close (); break; diff --git a/build/root.build b/build/root.build index 20ec18b..af83c1d 100644 --- a/build/root.build +++ b/build/root.build @@ -13,6 +13,11 @@ cxx{*}: extension = cxx cxx.poptions =+ "-I$out_root" "-I$src_root" +# While we don't have any C sources to compile we do run C tests (bdep-new) +# which need the path to the C compiler. +# +using c + # Load the cli module but only if it's available. This way a distribution # that includes pre-generated files can be built without installing cli. # This is also the reason why we need to explicitly spell out individual diff --git a/tests/ci.test b/tests/ci.test index 85155de..ed6f0c0 100644 --- a/tests/ci.test +++ b/tests/ci.test @@ -50,7 +50,7 @@ repository='http://example.com/prj.git' test.arguments += --yes --repository "$repository" --server "$server" \ --simulate 'success' -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! &prj/**/bootstrap/*** diff --git a/tests/config.test b/tests/config.test index 3dcd9f6..0500686 100644 --- a/tests/config.test +++ b/tests/config.test @@ -7,7 +7,7 @@ .include common.test project.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" status += -d prj init += -d prj diff --git a/tests/fetch.test b/tests/fetch.test index 0d0c1bc..d97bf63 100644 --- a/tests/fetch.test +++ b/tests/fetch.test @@ -4,7 +4,7 @@ .include common.test project.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! diff --git a/tests/init.test b/tests/init.test index 28f66e2..6214985 100644 --- a/tests/init.test +++ b/tests/init.test @@ -7,7 +7,7 @@ .include common.test project.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" status += -d prj deinit += -d prj diff --git a/tests/new.test b/tests/new.test index 6ae37e4..ee53668 100644 --- a/tests/new.test +++ b/tests/new.test @@ -8,7 +8,8 @@ # test.arguments += --no-checks -cxx = "config.cxx=$config.cxx" +c = config.c="$recall($c.path)" +cxx = config.cxx="$recall($cxx.path)" status += -d prj @@ -21,7 +22,7 @@ status += -d prj : exe { - $* --no-amalgamation -t exe -l c++ prj-foo 2>>/"EOE" &prj-foo/***; + $* -t exe -l c++ prj-foo 2>>/"EOE" &prj-foo/***; created new executable project prj-foo in $~/prj-foo/ EOE @@ -30,9 +31,20 @@ status += -d prj EOE } + : exe-c + { + $* -t exe -l c prj-foo 2>>/"EOE" &prj-foo/***; + created new executable project prj-foo in $~/prj-foo/ + EOE + + $build prj-foo/ $c 2>>~%EOE% + %(c|ld) .+%{2} + EOE + } + : lib { - $* --no-amalgamation -t lib -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; + $* -t lib -l c++ libprj-foo 2>>/"EOE" &libprj-foo/***; created new library project libprj-foo in $~/libprj-foo/ EOE @@ -41,6 +53,61 @@ status += -d prj EOE } + : lib-c + { + $* -t lib -l c libprj-foo 2>>/"EOE" &libprj-foo/***; + created new library project libprj-foo in $~/libprj-foo/ + EOE + + $build libprj-foo/ $c 2>>~%EOE% + %(version\.in|c|ar|ld) .+%{7} + EOE + } + + : exe-unit-tests + { + $* -t exe,unit-tests -l c++ foo 2>>/"EOE" &foo/***; + created new executable project foo in $~/foo/ + EOE + + $build foo/ $cxx 2>>~%EOE% + %(c\+\+|ld|ar) .+%{5} + EOE + } + + : exe-c-unit-tests + { + $* -t exe,unit-tests -l c foo 2>>/"EOE" &foo/***; + created new executable project foo in $~/foo/ + EOE + + $build foo/ $c 2>>~%EOE% + %(c|ld|ar) .+%{5} + EOE + } + + : lib-unit-tests + { + $* -t lib,unit-tests -l c++ libfoo 2>>/"EOE" &libfoo/***; + created new library project libfoo in $~/libfoo/ + EOE + + $build libfoo/ $cxx 2>>~%EOE% + %(version\.in|c\+\+|ar|ld) .+%{11} + EOE + } + + : lib-c-unit-tests + { + $* -t lib,unit-tests -l c libfoo 2>>/"EOE" &libfoo/***; + created new library project libfoo in $~/libfoo/ + EOE + + $build libfoo/ $c 2>>~%EOE% + %(version\.in|c|ar|ld) .+%{11} + EOE + } + : pkg : { diff --git a/tests/publish.test b/tests/publish.test index ba88e9a..0861931 100644 --- a/tests/publish.test +++ b/tests/publish.test @@ -30,7 +30,7 @@ end test.arguments += --repository "$repository" --yes \ --author-name user --author-email user@example.com -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! &prj/**/bootstrap/*** diff --git a/tests/status.test b/tests/status.test index 676dac0..2d60af5 100644 --- a/tests/status.test +++ b/tests/status.test @@ -4,7 +4,7 @@ .include common.test project.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! diff --git a/tests/sync.test b/tests/sync.test index b297990..4872f11 100644 --- a/tests/sync.test +++ b/tests/sync.test @@ -4,7 +4,7 @@ .include common.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! diff --git a/tests/test.test b/tests/test.test index 4ee43e0..667ee7a 100644 --- a/tests/test.test +++ b/tests/test.test @@ -4,7 +4,7 @@ .include common.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += cc "config.cxx=$config.cxx" -d prj 2>! diff --git a/tests/update.test b/tests/update.test index ed4bbd2..12be96e 100644 --- a/tests/update.test +++ b/tests/update.test @@ -7,7 +7,7 @@ .include common.test -cxx = cc "config.cxx=$config.cxx" +cxx = cc config.cxx="$recall($cxx.path)" new += 2>! init += $cxx -d prj 2>! -- cgit v1.1