f9e68d48d9
First of a planned series of end-to-end -n/--network join tests
(tests/integration/test_network_join_scenarios.cpp, [integration][root][net]):
two containers joined to the same intern network ping each other by IP,
run once with a real veth pair and once forced onto the tap+relay
fallback, since the two are genuinely different implementations. IPv6 is
deliberately excluded pending a known device-specific peculiarity.
split_lines_trimmed()/extract_marked_lines() moved from
test_rootless_run.cpp into tests/support/fixtures.{h,cpp} for reuse here.
wait_for_eth0_then() wraps a sandboxed command's own network-touching
script in a poll for eth0 to exist first: join_networks() runs
concurrently with, not before, the sandboxed command starting, so a
near-instant command can otherwise exit before its own join finishes --
the exact limitation already documented in network_join.{h,cpp}'s own
CLAUDE.md entry. Confirmed by testing (not assumed): without this, the
container's own immediate ping-and-exit sometimes raced ahead of the
veth-move step, which then failed outright ("Invalid netns value")
against an already-exited pid.
109 lines
5.0 KiB
C++
109 lines
5.0 KiB
C++
// Copyright (C) 2026 Viorel Munteanu
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#pragma once
|
|
|
|
#include <filesystem>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "config_file.h"
|
|
|
|
// The effective AppConfig for the current -t/--test run -- set once by
|
|
// run_self_tests() (self_test.cpp) from whatever main() resolved (respecting
|
|
// -c/--config-file, same as any other command), before Catch2 ever runs a
|
|
// TEST_CASE. Test code that creates a real container (test_rootless_run.cpp's
|
|
// own run_in_fixture()) reads this instead of hardcoding a fresh, default
|
|
// AppConfig{}, so e.g. `-c <all-unshare-off.yaml> -t -- "[integration][net]"`
|
|
// actually exercises what happens with pid/cgroup isolation disabled. Plain
|
|
// default-constructed AppConfig{} otherwise (matching every test's prior,
|
|
// unconditional behavior) -- a bare `-t` with no `-c` and no real config file
|
|
// changes nothing.
|
|
extern AppConfig g_test_app_config;
|
|
|
|
// Path to a real, runnable OCI Image Layout tar (something with an actual
|
|
// /bin/sh, unlike tests/gen_fixture.py's minimal single-file fixture used
|
|
// by the plain mount/unmount smoke test) for [integration][net] tests that
|
|
// need to actually run a command inside a container. Searches
|
|
// images/busybox.tar relative to the current working directory -- this
|
|
// project's own established manual-testing convention (see images/ at the
|
|
// repo root, gitignored). nullopt if not found; callers should SKIP()
|
|
// rather than fail, since fetching one is optional -- see
|
|
// tests/setup-tests.py.
|
|
std::optional<std::filesystem::path> find_busybox_fixture();
|
|
|
|
// RAII scratch XDG_CONFIG_HOME/XDG_STATE_HOME: for its lifetime, both env
|
|
// vars point at a fresh throwaway directory under /tmp, so
|
|
// config_file_path()/xdg_state_dir() (config_file.cpp/pid_file.cpp)
|
|
// resolve entirely under it instead of the real developer's own $HOME --
|
|
// integration tests that actually mount/run something must never touch
|
|
// real config/state. Restores whatever the two env vars were before (unset
|
|
// if they were unset) and removes the scratch directory on destruction.
|
|
class ScratchXdgDirs {
|
|
public:
|
|
ScratchXdgDirs();
|
|
~ScratchXdgDirs();
|
|
ScratchXdgDirs(const ScratchXdgDirs&) = delete;
|
|
ScratchXdgDirs& operator=(const ScratchXdgDirs&) = delete;
|
|
|
|
const std::filesystem::path& path() const { return path_; }
|
|
|
|
private:
|
|
std::filesystem::path path_;
|
|
std::optional<std::string> previous_config_home_;
|
|
std::optional<std::string> previous_state_home_;
|
|
};
|
|
|
|
// RAII stdout capture: for its lifetime, this process's own fd 1 (and
|
|
// anything a forked/exec'd child -- e.g. the real bwrap-sandboxed command,
|
|
// via dispatch_command()'s ordinary fork/exec chain -- inherits from it) is
|
|
// redirected to a throwaway temp file instead of the real terminal/pipe.
|
|
// contents() flushes C stdio first (so any of *this* process's own
|
|
// buffered writes land in the file before being read back) and returns
|
|
// everything captured so far; the destructor restores the original fd 1
|
|
// and removes the temp file. Kept narrowly scoped around just the call
|
|
// under test, so Catch2's own console reporter output is never captured
|
|
// by mistake.
|
|
class CapturedStdout {
|
|
public:
|
|
CapturedStdout();
|
|
~CapturedStdout();
|
|
CapturedStdout(const CapturedStdout&) = delete;
|
|
CapturedStdout& operator=(const CapturedStdout&) = delete;
|
|
|
|
std::string contents();
|
|
|
|
private:
|
|
int saved_fd_ = -1;
|
|
std::filesystem::path temp_path_;
|
|
};
|
|
|
|
// Splits `text` into non-empty, trailing-whitespace-trimmed lines.
|
|
std::vector<std::string> split_lines_trimmed(const std::string& text);
|
|
|
|
// Extracts the lines strictly between a "BEGIN-TEST-OUTPUT"/"END-TEST-OUTPUT"
|
|
// pair (each on its own line) -- empty if either marker is missing or out of
|
|
// order. CapturedStdout also captures slocker-lite's *own* status/log output
|
|
// (spdlog's default sink writes to stdout, not stderr) interleaved with
|
|
// whatever the sandboxed command itself prints, since both land on the same
|
|
// fd -- bracketing the sandboxed command's own real output between these two
|
|
// unique markers and extracting only what's strictly between them is robust
|
|
// regardless of whatever else slocker-lite itself prints, since in practice
|
|
// all of that happens before the sandboxed command gets to run its own first
|
|
// command at all.
|
|
std::vector<std::string> extract_marked_lines(const std::string& text);
|