Add test support helpers + [integration] config/bwrap chain tests

tests/support/fixtures.{h,cpp}: find_busybox_fixture() (images/busybox.tar
relative to cwd, this project's own established manual-testing convention
-- nullopt if absent, so [net] tests can SKIP() rather than fail) and
ScratchXdgDirs, an RAII helper pointing XDG_CONFIG_HOME/XDG_STATE_HOME at a
fresh throwaway mkdtemp() directory for its lifetime, restoring the
previous environment and removing the directory on destruction -- so
integration tests that actually exercise config_file_path()/xdg_state_dir()
never touch the real developer's own config/state.

tests/integration/test_config_bwrap_chain.cpp: the user's own example --
write a config file, load it back, resolve a NamespaceConfig from it the
same way run_container() (commands.cpp) does, and confirm build_bwrap_args()'s
resulting argv actually reflects it (disabled unshare-net/unshare-uts never
requested; an all-default config matches the live host's own
detect_bwrap_unshare_args() probe exactly). Neither test mounts/runs
anything or needs privilege.

Real bug found via ~10-30 repeated combined [unit]+[integration] runs, not
assumed: ScratchXdgDirs's constructor built its mkdtemp() template vector
from two *separate* temporary std::string objects (`.begin()` off one,
`.end()` off the other) -- mixing iterators from different containers is
undefined behavior, here manifesting as an intermittent, heap-address-
dependent `std::length_error: cannot create std::vector larger than
max_size()` inside whichever test happened to run adjacent to it. Fixed by
using a single named string instance for both ends of the range; confirmed
clean across 30 repeated combined runs afterward, plus a full run as root.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-09-04 09:49:51 +00:00
parent 42f9d36edf
commit dad4e75392
4 changed files with 244 additions and 1 deletions
+83
View File
@@ -0,0 +1,83 @@
// 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 <cstdlib>
#include <system_error>
#include <vector>
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);
}