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:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user