aboutsummaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/libmain/crash.cc56
-rw-r--r--tests/unit/libstore/filetransfer.cc8
-rw-r--r--tests/unit/libutil/async-collect.cc104
-rw-r--r--tests/unit/libutil/async-semaphore.cc74
-rw-r--r--tests/unit/libutil/compression.cc3
-rw-r--r--tests/unit/meson.build18
6 files changed, 259 insertions, 4 deletions
diff --git a/tests/unit/libmain/crash.cc b/tests/unit/libmain/crash.cc
new file mode 100644
index 000000000..883dc39bd
--- /dev/null
+++ b/tests/unit/libmain/crash.cc
@@ -0,0 +1,56 @@
+#include <gtest/gtest.h>
+#include "crash-handler.hh"
+
+namespace nix {
+
+class OopsException : public std::exception
+{
+ const char * msg;
+
+public:
+ OopsException(const char * msg) : msg(msg) {}
+ const char * what() const noexcept override
+ {
+ return msg;
+ }
+};
+
+void causeCrashForTesting(std::function<void()> fixture)
+{
+ registerCrashHandler();
+ std::cerr << "time to crash\n";
+ try {
+ fixture();
+ } catch (...) {
+ std::terminate();
+ }
+}
+
+TEST(CrashHandler, exceptionName)
+{
+ ASSERT_DEATH(
+ causeCrashForTesting([]() { throw OopsException{"lol oops"}; }),
+ "time to crash\nLix crashed.*OopsException: lol oops"
+ );
+}
+
+TEST(CrashHandler, unknownTerminate)
+{
+ ASSERT_DEATH(
+ causeCrashForTesting([]() { std::terminate(); }),
+ "time to crash\nLix crashed.*std::terminate\\(\\) called without exception"
+ );
+}
+
+TEST(CrashHandler, nonStdException)
+{
+ ASSERT_DEATH(
+ causeCrashForTesting([]() {
+ // NOLINTNEXTLINE(hicpp-exception-baseclass): intentional
+ throw 4;
+ }),
+ "time to crash\nLix crashed.*Unknown exception! Spooky\\."
+ );
+}
+
+}
diff --git a/tests/unit/libstore/filetransfer.cc b/tests/unit/libstore/filetransfer.cc
index 71e7392fc..fd4d326f0 100644
--- a/tests/unit/libstore/filetransfer.cc
+++ b/tests/unit/libstore/filetransfer.cc
@@ -150,6 +150,14 @@ TEST(FileTransfer, exceptionAbortsDownload)
}
}
+TEST(FileTransfer, exceptionAbortsRead)
+{
+ auto [port, srv] = serveHTTP("200 ok", "content-length: 0\r\n", [] { return ""; });
+ auto ft = makeFileTransfer();
+ char buf[10] = "";
+ ASSERT_THROW(ft->download(FileTransferRequest(fmt("http://[::1]:%d/index", port)))->read(buf, 10), EndOfFile);
+}
+
TEST(FileTransfer, NOT_ON_DARWIN(reportsSetupErrors))
{
auto [port, srv] = serveHTTP("404 not found", "", [] { return ""; });
diff --git a/tests/unit/libutil/async-collect.cc b/tests/unit/libutil/async-collect.cc
new file mode 100644
index 000000000..770374d21
--- /dev/null
+++ b/tests/unit/libutil/async-collect.cc
@@ -0,0 +1,104 @@
+#include "async-collect.hh"
+
+#include <gtest/gtest.h>
+#include <kj/array.h>
+#include <kj/async.h>
+#include <kj/exception.h>
+#include <stdexcept>
+
+namespace nix {
+
+TEST(AsyncCollect, void)
+{
+ kj::EventLoop loop;
+ kj::WaitScope waitScope(loop);
+
+ auto a = kj::newPromiseAndFulfiller<void>();
+ auto b = kj::newPromiseAndFulfiller<void>();
+ auto c = kj::newPromiseAndFulfiller<void>();
+ auto d = kj::newPromiseAndFulfiller<void>();
+
+ auto collect = asyncCollect(kj::arr(
+ std::pair(1, std::move(a.promise)),
+ std::pair(2, std::move(b.promise)),
+ std::pair(3, std::move(c.promise)),
+ std::pair(4, std::move(d.promise))
+ ));
+
+ auto p = collect.next();
+ ASSERT_FALSE(p.poll(waitScope));
+
+ // collection is ordered
+ c.fulfiller->fulfill();
+ b.fulfiller->fulfill();
+
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_EQ(p.wait(waitScope), 3);
+
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_EQ(p.wait(waitScope), 2);
+
+ p = collect.next();
+ ASSERT_FALSE(p.poll(waitScope));
+
+ // exceptions propagate
+ a.fulfiller->rejectIfThrows([] { throw std::runtime_error("test"); });
+
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_THROW(p.wait(waitScope), kj::Exception);
+
+ // first exception aborts collection
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_THROW(p.wait(waitScope), kj::Exception);
+}
+
+TEST(AsyncCollect, nonVoid)
+{
+ kj::EventLoop loop;
+ kj::WaitScope waitScope(loop);
+
+ auto a = kj::newPromiseAndFulfiller<int>();
+ auto b = kj::newPromiseAndFulfiller<int>();
+ auto c = kj::newPromiseAndFulfiller<int>();
+ auto d = kj::newPromiseAndFulfiller<int>();
+
+ auto collect = asyncCollect(kj::arr(
+ std::pair(1, std::move(a.promise)),
+ std::pair(2, std::move(b.promise)),
+ std::pair(3, std::move(c.promise)),
+ std::pair(4, std::move(d.promise))
+ ));
+
+ auto p = collect.next();
+ ASSERT_FALSE(p.poll(waitScope));
+
+ // collection is ordered
+ c.fulfiller->fulfill(1);
+ b.fulfiller->fulfill(2);
+
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_EQ(p.wait(waitScope), std::pair(3, 1));
+
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_EQ(p.wait(waitScope), std::pair(2, 2));
+
+ p = collect.next();
+ ASSERT_FALSE(p.poll(waitScope));
+
+ // exceptions propagate
+ a.fulfiller->rejectIfThrows([] { throw std::runtime_error("test"); });
+
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_THROW(p.wait(waitScope), kj::Exception);
+
+ // first exception aborts collection
+ p = collect.next();
+ ASSERT_TRUE(p.poll(waitScope));
+ ASSERT_THROW(p.wait(waitScope), kj::Exception);
+}
+}
diff --git a/tests/unit/libutil/async-semaphore.cc b/tests/unit/libutil/async-semaphore.cc
new file mode 100644
index 000000000..12b52885d
--- /dev/null
+++ b/tests/unit/libutil/async-semaphore.cc
@@ -0,0 +1,74 @@
+#include "async-semaphore.hh"
+
+#include <gtest/gtest.h>
+#include <kj/async.h>
+
+namespace nix {
+
+TEST(AsyncSemaphore, counting)
+{
+ kj::EventLoop loop;
+ kj::WaitScope waitScope(loop);
+
+ AsyncSemaphore sem(2);
+
+ ASSERT_EQ(sem.available(), 2);
+ ASSERT_EQ(sem.used(), 0);
+
+ auto a = kj::evalNow([&] { return sem.acquire(); });
+ ASSERT_EQ(sem.available(), 1);
+ ASSERT_EQ(sem.used(), 1);
+ auto b = kj::evalNow([&] { return sem.acquire(); });
+ ASSERT_EQ(sem.available(), 0);
+ ASSERT_EQ(sem.used(), 2);
+
+ auto c = kj::evalNow([&] { return sem.acquire(); });
+ auto d = kj::evalNow([&] { return sem.acquire(); });
+
+ ASSERT_TRUE(a.poll(waitScope));
+ ASSERT_TRUE(b.poll(waitScope));
+ ASSERT_FALSE(c.poll(waitScope));
+ ASSERT_FALSE(d.poll(waitScope));
+
+ a = nullptr;
+ ASSERT_TRUE(c.poll(waitScope));
+ ASSERT_FALSE(d.poll(waitScope));
+
+ {
+ auto lock = b.wait(waitScope);
+ ASSERT_FALSE(d.poll(waitScope));
+ }
+
+ ASSERT_TRUE(d.poll(waitScope));
+
+ ASSERT_EQ(sem.available(), 0);
+ ASSERT_EQ(sem.used(), 2);
+ c = nullptr;
+ ASSERT_EQ(sem.available(), 1);
+ ASSERT_EQ(sem.used(), 1);
+ d = nullptr;
+ ASSERT_EQ(sem.available(), 2);
+ ASSERT_EQ(sem.used(), 0);
+}
+
+TEST(AsyncSemaphore, cancelledWaiter)
+{
+ kj::EventLoop loop;
+ kj::WaitScope waitScope(loop);
+
+ AsyncSemaphore sem(1);
+
+ auto a = kj::evalNow([&] { return sem.acquire(); });
+ auto b = kj::evalNow([&] { return sem.acquire(); });
+ auto c = kj::evalNow([&] { return sem.acquire(); });
+
+ ASSERT_TRUE(a.poll(waitScope));
+ ASSERT_FALSE(b.poll(waitScope));
+
+ b = nullptr;
+ a = nullptr;
+
+ ASSERT_TRUE(c.poll(waitScope));
+}
+
+}
diff --git a/tests/unit/libutil/compression.cc b/tests/unit/libutil/compression.cc
index 8d181f53d..6dd8c96df 100644
--- a/tests/unit/libutil/compression.cc
+++ b/tests/unit/libutil/compression.cc
@@ -1,4 +1,5 @@
#include "compression.hh"
+#include <cstddef>
#include <gtest/gtest.h>
namespace nix {
@@ -147,7 +148,7 @@ TEST_P(PerTypeNonNullCompressionTest, truncatedValidInput)
/* n.b. This also tests zero-length input, which is also invalid.
* As of the writing of this comment, it returns empty output, but is
* allowed to throw a compression error instead. */
- for (int i = 0; i < compressed.length(); ++i) {
+ for (size_t i = 0u; i < compressed.length(); ++i) {
auto newCompressed = compressed.substr(compressed.length() - i);
try {
decompress(method, newCompressed);
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 8ff0b5ec5..9db619c5d 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -12,7 +12,11 @@
# It's only ~200 lines; better to just refactor the tests themselves which we'll want to do anyway.
default_test_env = {
- 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1'
+ 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1',
+ # Prevents loading global configuration file in /etc/nix/nix.conf in tests 😱
+ 'NIX_CONF_DIR': '/var/empty',
+ # Prevent loading user configuration files in tests
+ 'NIX_USER_CONF_FILES': '',
}
libutil_test_support_sources = files(
@@ -39,6 +43,8 @@ liblixutil_test_support = declare_dependency(
)
libutil_tests_sources = files(
+ 'libutil/async-collect.cc',
+ 'libutil/async-semaphore.cc',
'libutil/canon-path.cc',
'libutil/checked-arithmetic.cc',
'libutil/chunked-vector.cc',
@@ -76,6 +82,7 @@ libutil_tester = executable(
liblixexpr_mstatic,
liblixutil_test_support,
nlohmann_json,
+ kj,
],
cpp_pch : cpp_pch,
)
@@ -252,7 +259,7 @@ test(
env : default_test_env + {
# No special meaning here, it's just a file laying around that is unlikely to go anywhere
# any time soon.
- '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix',
+ '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/legacy/buildenv.nix',
# Use a temporary home directory for the unit tests.
# Otherwise, /homeless-shelter is created in the single-user sandbox, and functional tests will fail.
# TODO(alois31): handle TMPDIR properly (meson can't, and setting HOME in the test is too late)…
@@ -262,9 +269,14 @@ test(
protocol : 'gtest',
)
+libmain_tests_sources = files(
+ 'libmain/crash.cc',
+ 'libmain/progress-bar.cc',
+)
+
libmain_tester = executable(
'liblixmain-tests',
- files('libmain/progress-bar.cc'),
+ libmain_tests_sources,
dependencies : [
liblixmain,
liblixexpr,