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.
150 lines
4.8 KiB
C++
150 lines
4.8 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.
|
|
|
|
#include "fixtures.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <system_error>
|
|
#include <vector>
|
|
|
|
AppConfig g_test_app_config;
|
|
|
|
std::optional<std::filesystem::path> find_busybox_fixture() {
|
|
std::error_code ec;
|
|
auto path = std::filesystem::current_path(ec) / "images" / "busybox.tar";
|
|
if (ec || !std::filesystem::exists(path, ec) || ec) {
|
|
return std::nullopt;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
namespace {
|
|
|
|
std::optional<std::string> getenv_opt(const char* name) {
|
|
const char* value = std::getenv(name);
|
|
if (!value) {
|
|
return std::nullopt;
|
|
}
|
|
return std::string(value);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ScratchXdgDirs::ScratchXdgDirs() {
|
|
// Real bug found by testing, not assumed: an earlier version built
|
|
// this vector from `std::string("...").begin()` paired with a
|
|
// *separate* `std::string("...").end()` -- two distinct temporary
|
|
// objects, even though they held identical content. Mixing iterators
|
|
// from two different containers when constructing a third is
|
|
// undefined behavior; here it manifested as an intermittent (SSO/heap
|
|
// address dependent, hence flaky) `std::length_error: cannot create
|
|
// std::vector larger than max_size()` from a garbage begin/end
|
|
// distance, confirmed via ~10 repeated runs of the [integration] tests
|
|
// together before it reproduced. Fixed by using one string instance
|
|
// for both ends of the range.
|
|
std::string tmpl_str = "/tmp/slocker-lite-test-XXXXXX";
|
|
std::vector<char> tmpl(tmpl_str.begin(), tmpl_str.end());
|
|
tmpl.push_back('\0');
|
|
char* created = mkdtemp(tmpl.data());
|
|
path_ = created ? std::filesystem::path(created) : std::filesystem::temp_directory_path() / "slocker-lite-test";
|
|
std::filesystem::create_directories(path_);
|
|
|
|
previous_config_home_ = getenv_opt("XDG_CONFIG_HOME");
|
|
previous_state_home_ = getenv_opt("XDG_STATE_HOME");
|
|
setenv("XDG_CONFIG_HOME", path_.c_str(), 1);
|
|
setenv("XDG_STATE_HOME", path_.c_str(), 1);
|
|
}
|
|
|
|
ScratchXdgDirs::~ScratchXdgDirs() {
|
|
if (previous_config_home_) {
|
|
setenv("XDG_CONFIG_HOME", previous_config_home_->c_str(), 1);
|
|
} else {
|
|
unsetenv("XDG_CONFIG_HOME");
|
|
}
|
|
if (previous_state_home_) {
|
|
setenv("XDG_STATE_HOME", previous_state_home_->c_str(), 1);
|
|
} else {
|
|
unsetenv("XDG_STATE_HOME");
|
|
}
|
|
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(path_, ec);
|
|
}
|
|
|
|
CapturedStdout::CapturedStdout() {
|
|
std::string tmpl_str = "/tmp/slocker-lite-test-stdout-XXXXXX";
|
|
std::vector<char> tmpl(tmpl_str.begin(), tmpl_str.end());
|
|
tmpl.push_back('\0');
|
|
int fd = mkstemp(tmpl.data());
|
|
temp_path_ = tmpl.data();
|
|
|
|
fflush(stdout);
|
|
saved_fd_ = dup(STDOUT_FILENO);
|
|
dup2(fd, STDOUT_FILENO);
|
|
close(fd);
|
|
}
|
|
|
|
CapturedStdout::~CapturedStdout() {
|
|
fflush(stdout);
|
|
if (saved_fd_ >= 0) {
|
|
dup2(saved_fd_, STDOUT_FILENO);
|
|
close(saved_fd_);
|
|
}
|
|
std::error_code ec;
|
|
std::filesystem::remove(temp_path_, ec);
|
|
}
|
|
|
|
std::string CapturedStdout::contents() {
|
|
fflush(stdout);
|
|
std::ifstream in(temp_path_);
|
|
std::ostringstream out;
|
|
out << in.rdbuf();
|
|
return out.str();
|
|
}
|
|
|
|
std::vector<std::string> split_lines_trimmed(const std::string& text) {
|
|
std::vector<std::string> lines;
|
|
std::istringstream iss(text);
|
|
std::string line;
|
|
while (std::getline(iss, line)) {
|
|
while (!line.empty() && std::isspace(static_cast<unsigned char>(line.back()))) {
|
|
line.pop_back();
|
|
}
|
|
if (!line.empty()) {
|
|
lines.push_back(line);
|
|
}
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
std::vector<std::string> extract_marked_lines(const std::string& text) {
|
|
auto lines = split_lines_trimmed(text);
|
|
auto begin = std::find(lines.begin(), lines.end(), "BEGIN-TEST-OUTPUT");
|
|
auto end = std::find(lines.begin(), lines.end(), "END-TEST-OUTPUT");
|
|
if (begin == lines.end() || end == lines.end() || end <= begin) {
|
|
return {};
|
|
}
|
|
return std::vector<std::string>(begin + 1, end);
|
|
}
|