Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ cc*.s
/build/
/build-*/

# Nested test-runner project's generated config
/test/runner/tup.config

# Tup database
.tup/

Expand Down
27 changes: 4 additions & 23 deletions test/unit/e2e_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@

#include "e2e_fixture.hpp"

#include "temp_root.hpp"

#include "pup/core/global_pool.hpp"
#include "pup/core/string_pool.hpp"

#include <cstdlib>
#include <fstream>
#include <random>

#include <cstdio>

Expand All @@ -24,26 +25,6 @@ auto intern(std::string_view s) -> StringId { return global_pool().intern(s); }

namespace {

auto generate_temp_dir() -> fs::path
{
auto const* tmpdir = std::getenv("TMPDIR");
if (!tmpdir)
tmpdir = "/tmp/claude";

auto base = fs::path { tmpdir };
if (!fs::exists(base))
fs::create_directories(base);

// Generate random suffix
auto rng = std::random_device {};
auto dist = std::uniform_int_distribution<unsigned int> { 0, 0xFFFFFFFF };
auto suffix = std::to_string(dist(rng));

auto result = base / ("e2e_" + suffix);
fs::create_directories(result);
return result;
}

auto copy_fixture(fs::path const& src, fs::path const& dst) -> void
{
for (auto const& entry : fs::recursive_directory_iterator(src)) {
Expand Down Expand Up @@ -73,7 +54,7 @@ auto copy_fixture(fs::path const& src, fs::path const& dst) -> void

E2EFixture::E2EFixture(std::string_view name)
: m_name { name }
, m_workdir { generate_temp_dir() }
, m_workdir { temp_dir("e2e") }
, m_fixture_dir { get_fixtures_dir() / name }
, m_pup_binary { get_pup_binary() }
{
Expand Down Expand Up @@ -337,7 +318,7 @@ auto run_shell_fixture(std::string_view name) -> ProcessResult
}

// Create temp directory
auto workdir = generate_temp_dir();
auto workdir = temp_dir("e2e");
copy_fixture(fixture_dir, workdir);

// Copy test.sh too for shell fixtures
Expand Down
79 changes: 79 additions & 0 deletions test/unit/temp_root.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Putup authors

#pragma once

#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <random>
#include <string>
#include <string_view>

namespace pup::test {

namespace detail {

inline auto claim_root(std::filesystem::path const& base) -> std::filesystem::path
{
auto rng = std::random_device {};
auto dist = std::uniform_int_distribution<unsigned int> { 0, 0xFFFFFFFF };
for (auto attempt = 0; attempt < 64; ++attempt) {
auto candidate = base / ("pup_test_" + std::to_string(dist(rng)));
auto ec = std::error_code {};
if (std::filesystem::create_directories(candidate, ec) && !ec) {
return candidate;
}
}
return {};
}

inline auto resolve_root() -> std::filesystem::path
{
// Scratch must be outside any putup project tree: a project rejects files under it that no rule owns.
auto ec = std::error_code {};
if (auto ambient = std::filesystem::temp_directory_path(ec); !ec && !ambient.empty()) {
if (auto root = claim_root(ambient); !root.empty()) {
return root;
}
}

#ifndef _WIN32
// e2e fixtures exec binaries from scratch, so a host relying on /dev/shm needs it mounted exec.
for (auto const* candidate : { "/var/tmp", "/dev/shm" }) {
if (auto root = claim_root(candidate); !root.empty()) {
return root;
}
}
#endif

std::fprintf(stderr, "temp_root: no writable scratch directory\n");
std::abort();
}

} // namespace detail

/// Writable scratch directory for this process, resolved once.
inline auto temp_root() -> std::filesystem::path const&
{
static auto const root = detail::resolve_root();
return root;
}

/// A path under this process's scratch directory; the caller creates whatever it needs there.
inline auto temp_path(std::string_view stem) -> std::filesystem::path
{
return temp_root() / std::string { stem };
}

/// A created directory, unique within this process and across concurrent shards.
inline auto temp_dir(std::string_view stem) -> std::filesystem::path
{
static auto counter = std::atomic<unsigned> { 0 };
auto path = temp_root() / (std::string { stem } + "_" + std::to_string(counter.fetch_add(1)));
std::filesystem::create_directories(path);
return path;
}

} // namespace pup::test
6 changes: 3 additions & 3 deletions test/unit/test_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2024 Putup authors

#include "catch_amalgamated.hpp"
#include "temp_root.hpp"

#include "pup/core/global_pool.hpp"
#include "pup/core/string_pool.hpp"
#include "pup/graph/builder.hpp"
Expand Down Expand Up @@ -83,9 +85,7 @@ class BuilderTestFixture {
public:
BuilderTestFixture()
{
test_root_ = fs::temp_directory_path()
/ ("pup_test_builder_" + std::to_string(std::random_device {}()));
fs::create_directories(test_root_);
test_root_ = pup::test::temp_dir("pup_test_builder");
fs::create_directories(test_root_ / "src");
fs::create_directories(test_root_ / "include");
fs::create_directories(test_root_ / "include" / "generated");
Expand Down
17 changes: 11 additions & 6 deletions test/unit/test_exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2024 Putup authors

#include "catch_amalgamated.hpp"
#include "temp_root.hpp"

#include "pup/core/global_pool.hpp"
#include "pup/core/string_pool.hpp"
#include "pup/exec/progress_display.hpp"
Expand All @@ -12,6 +14,7 @@

#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <thread>

using namespace pup;
Expand Down Expand Up @@ -59,12 +62,14 @@ TEST_CASE("CommandRunner basic execution", "[exec]")

SECTION("working directory")
{
auto opts = RunOptions { .working_dir = intern("/tmp") };
auto const workdir = pup::test::temp_dir("pup_exec_wd").string();
auto opts = RunOptions { .working_dir = intern(workdir) };
auto result = runner.run("pwd", opts);
REQUIRE(result.has_value());
REQUIRE(result->exit_code == 0);
// May have trailing newline and/or resolve to /private/tmp on macOS
REQUIRE(sv(result->stdout_output).find("tmp") != std::string_view::npos);
// The path may come back resolved (/tmp is /private/tmp on macOS), so match the leaf.
auto const leaf = std::filesystem::path { workdir }.filename().string();
REQUIRE(sv(result->stdout_output).find(leaf) != std::string_view::npos);
}

SECTION("environment variable")
Expand Down Expand Up @@ -408,7 +413,7 @@ TEST_CASE("Scheduler exported_vars", "[exec]")

auto output_id = graph::add_file_node(bs.graph, graph::FileNode {
.type = NodeType::Generated,
.name = intern("/tmp/test_output.txt"),
.name = intern(pup::test::temp_path("test_output.txt").string()),
});

(void)graph::add_edge(bs.graph, *input_id, *cmd_id);
Expand Down Expand Up @@ -458,7 +463,7 @@ TEST_CASE("Scheduler exported_vars", "[exec]")

auto output_id = graph::add_file_node(bs.graph, graph::FileNode {
.type = NodeType::Generated,
.name = intern("/tmp/test_export_order.txt"),
.name = intern(pup::test::temp_path("test_export_order.txt").string()),
});

(void)graph::add_edge(bs.graph, *input_id, *cmd_id);
Expand Down Expand Up @@ -501,7 +506,7 @@ TEST_CASE("Scheduler exported_vars", "[exec]")

auto output_id = graph::add_file_node(bs.graph, graph::FileNode {
.type = NodeType::Generated,
.name = intern("/tmp/test_output2.txt"),
.name = intern(pup::test::temp_path("test_output2.txt").string()),
});

(void)graph::add_edge(bs.graph, *input_id, *cmd_id);
Expand Down
12 changes: 3 additions & 9 deletions test/unit/test_glob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2024 Putup authors

#include "catch_amalgamated.hpp"
#include "temp_root.hpp"

#include "pup/core/global_pool.hpp"
#include "pup/core/string_pool.hpp"
#include "pup/parser/glob.hpp"
Expand All @@ -28,15 +30,7 @@ class TempDir {
// Test shards run as concurrent processes, so the name must be unique across them.
TempDir()
{
auto rng = std::random_device {};
auto dist = std::uniform_int_distribution<unsigned int> { 0, 0xFFFFFFFF };
for (;;) {
auto candidate = fs::temp_directory_path() / ("pup_glob_" + std::to_string(dist(rng)));
if (fs::create_directory(candidate)) {
path_ = candidate;
return;
}
}
path_ = pup::test::temp_dir("pup_glob");
}

~TempDir()
Expand Down
20 changes: 11 additions & 9 deletions test/unit/test_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2024 Putup authors

#include "catch_amalgamated.hpp"
#include "temp_root.hpp"

#include "pup/cli/index_serialize.hpp"
#include "pup/core/global_pool.hpp"
#include "pup/core/hash.hpp"
Expand Down Expand Up @@ -479,7 +481,7 @@ TEST_CASE("Index serialization roundtrip", "[e2e][index]")
REQUIRE(data->size() > sizeof(RawHeader) + sizeof(RawFooter));

// Write to temp file and read back
auto temp_path = (std::filesystem::temp_directory_path() / "pup_test_index").string();
auto temp_path = pup::test::temp_path("pup_test_index").string();

auto write_result = write_index(temp_path, index);
REQUIRE(write_result.has_value());
Expand Down Expand Up @@ -615,7 +617,7 @@ TEST_CASE("Index ID contiguity requirement", "[e2e][index]")
});

// Serialize and read back
auto temp_path = (std::filesystem::temp_directory_path() / "pup_test_contiguous").string();
auto temp_path = pup::test::temp_path("pup_test_contiguous").string();
auto write_result = write_index(temp_path, index);
REQUIRE(write_result.has_value());

Expand Down Expand Up @@ -665,7 +667,7 @@ TEST_CASE("A record whose declared layout does not fit the file is refused", "[e
auto const data = serialize_index(index);
REQUIRE(data.has_value());

auto const path = (std::filesystem::temp_directory_path() / "pup_malicious_test").string();
auto const path = pup::test::temp_path("pup_malicious_test").string();
auto const past_end = static_cast<std::uint32_t>(data->size()) + 1000;
auto const overflowing_count = static_cast<std::uint32_t>(data->size() / sizeof(RawFileEntry)) + 100;

Expand Down Expand Up @@ -764,7 +766,7 @@ TEST_CASE("A command longer than the string table's entry limit is still recorde
.display = intern(oversized.view()),
});

auto temp_path = (std::filesystem::temp_directory_path() / "pup_oversized_command_test").string();
auto temp_path = pup::test::temp_path("pup_oversized_command_test").string();
REQUIRE(write_index(temp_path, index).has_value());

auto opened = open_index(temp_path);
Expand All @@ -785,7 +787,7 @@ TEST_CASE("A command longer than the string table's entry limit is still recorde
exact.add_file(FileEntry { .id = 1, .parent_id = 0, .type = NodeType::Directory, .name = intern("dir") });
exact.add_command(CommandEntry { .id = cmd_id, .dir_id = 1, .instruction_pattern = intern(at_limit.view()) });

auto exact_path = (std::filesystem::temp_directory_path() / "pup_at_limit_command_test").string();
auto exact_path = pup::test::temp_path("pup_at_limit_command_test").string();
REQUIRE(write_index(exact_path, exact).has_value());

auto exact_opened = open_index(exact_path);
Expand Down Expand Up @@ -864,7 +866,7 @@ TEST_CASE("A command with more than 255 operands records all of them", "[index]"
.outputs = outputs,
});

auto temp_path = (std::filesystem::temp_directory_path() / "pup_many_operands_test").string();
auto temp_path = pup::test::temp_path("pup_many_operands_test").string();
REQUIRE(write_index(temp_path, index).has_value());

auto opened = open_index(temp_path);
Expand Down Expand Up @@ -1232,7 +1234,7 @@ TEST_CASE("v8 roundtrip with operand sections", "[e2e][index][v8]")
REQUIRE(data.has_value());

// Write and read back
auto temp_path = (std::filesystem::temp_directory_path() / "pup_v8_roundtrip_test").string();
auto temp_path = pup::test::temp_path("pup_v8_roundtrip_test").string();
auto write_result = write_index(temp_path, index);
REQUIRE(write_result.has_value());

Expand Down Expand Up @@ -1356,7 +1358,7 @@ auto require_graph_index_roundtrip(pup::graph::BuildGraph const& bs, std::string
}
}

auto temp_path = (std::filesystem::temp_directory_path() / file_tag).string();
auto temp_path = pup::test::temp_path(file_tag).string();
auto write_result = write_index(temp_path, index);
REQUIRE(write_result.has_value());

Expand Down Expand Up @@ -1524,7 +1526,7 @@ auto write_bytes(std::string const& path, Vec<std::byte> const& bytes) -> void

auto temp_index_path(std::string_view stem) -> std::string
{
return (std::filesystem::temp_directory_path() / std::string { stem }).string();
return pup::test::temp_path(std::string { stem }).string();
}

/// A record with one file of every type, laid out under a directory chain so the recovered
Expand Down
15 changes: 3 additions & 12 deletions test/unit/test_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright (c) 2024 Putup authors

#include "catch_amalgamated.hpp"
#include "temp_root.hpp"

#include "pup/core/global_pool.hpp"
#include "pup/core/layout.hpp"
#include "pup/core/string_pool.hpp"
Expand All @@ -22,20 +24,9 @@ namespace {
/// RAII helper to create a temporary directory tree for testing
class TempDir {
public:
// Shards run concurrently as separate processes, so the name must be unique
// across processes: std::rand() is unseeded and yields the same sequence in
// every one of them.
TempDir()
: path_ { pup::test::temp_dir("pup_test") }
{
auto rng = std::random_device {};
auto dist = std::uniform_int_distribution<unsigned int> { 0, 0xFFFFFFFF };
for (;;) {
auto candidate = fs::temp_directory_path() / ("pup_test_" + std::to_string(dist(rng)));
if (fs::create_directory(candidate)) {
path_ = candidate;
return;
}
}
}

~TempDir()
Expand Down
Loading
Loading