Files
ceamac f9e68d48d9 Add intern-network IP ping tests (veth + tap+relay variants)
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.
2026-09-05 11:38:21 +00:00

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);