forked from lix-project/lix
Add error reporting to machine spec paser
Currently machine specification (`/etc/nix/machine`) parser fails with a vague exception if the file had incorrect format. This commit adds verbose exceptions and unit-tests for the parser.
This commit is contained in:
parent
64a3b045c1
commit
e989c83b44
|
@ -1,4 +1,3 @@
|
||||||
#include "machines.hh"
|
|
||||||
#include "worker.hh"
|
#include "worker.hh"
|
||||||
#include "substitution-goal.hh"
|
#include "substitution-goal.hh"
|
||||||
#include "derivation-goal.hh"
|
#include "derivation-goal.hh"
|
||||||
|
|
|
@ -83,53 +83,83 @@ ref<Store> Machine::openStore() const {
|
||||||
return nix::openStore(storeUri, storeParams);
|
return nix::openStore(storeUri, storeParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parseMachines(const std::string & s, Machines & machines)
|
static std::vector<std::string> expandBuilderLines(const std::string& builders) {
|
||||||
{
|
std::vector<std::string> result;
|
||||||
for (auto line : tokenizeString<std::vector<string>>(s, "\n;")) {
|
for (auto line : tokenizeString<std::vector<string>>(builders, "\n;")) {
|
||||||
trim(line);
|
trim(line);
|
||||||
line.erase(std::find(line.begin(), line.end(), '#'), line.end());
|
line.erase(std::find(line.begin(), line.end(), '#'), line.end());
|
||||||
if (line.empty()) continue;
|
if (line.empty()) continue;
|
||||||
|
|
||||||
if (line[0] == '@') {
|
if (line[0] == '@') {
|
||||||
auto file = trim(std::string(line, 1));
|
const std::string path = trim(std::string(line, 1));
|
||||||
|
std::string text;
|
||||||
try {
|
try {
|
||||||
parseMachines(readFile(file), machines);
|
text = readFile(path);
|
||||||
} catch (const SysError & e) {
|
} catch (const SysError & e) {
|
||||||
if (e.errNo != ENOENT)
|
if (e.errNo != ENOENT)
|
||||||
throw;
|
throw;
|
||||||
debug("cannot find machines file '%s'", file);
|
debug("cannot find machines file '%s'", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto lines = expandBuilderLines(text);
|
||||||
|
result.insert(end(result), begin(lines), end(lines));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tokens = tokenizeString<std::vector<string>>(line);
|
result.emplace_back(line);
|
||||||
auto sz = tokens.size();
|
}
|
||||||
if (sz < 1)
|
return result;
|
||||||
throw FormatError("bad machine specification '%s'", line);
|
}
|
||||||
|
|
||||||
auto isSet = [&](size_t n) {
|
static Machine parseBuilderLine(const std::string& line) {
|
||||||
return tokens.size() > n && tokens[n] != "" && tokens[n] != "-";
|
const auto tokens = tokenizeString<std::vector<string>>(line);
|
||||||
};
|
|
||||||
|
|
||||||
machines.emplace_back(tokens[0],
|
auto isSet = [&](size_t fieldIndex) {
|
||||||
|
return tokens.size() > fieldIndex && tokens[fieldIndex] != "" && tokens[fieldIndex] != "-";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parseUnsignedIntField = [&](size_t fieldIndex) {
|
||||||
|
const auto result = string2Int<unsigned int>(tokens[fieldIndex]);
|
||||||
|
if (!result) {
|
||||||
|
throw FormatError("bad machine specification: failed to convert column #%lu in a row: '%s' to 'unsigned int'", fieldIndex, line);
|
||||||
|
}
|
||||||
|
return result.value();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ensureBase64 = [&](size_t fieldIndex) {
|
||||||
|
const auto& str = tokens[fieldIndex];
|
||||||
|
try {
|
||||||
|
base64Decode(str);
|
||||||
|
} catch (const Error& e) {
|
||||||
|
throw FormatError("bad machine specification: a column #%lu in a row: '%s' is not valid base64 string: %s", fieldIndex, line, e.what());
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isSet(0)) {
|
||||||
|
throw FormatError("bad machine specification: store URI was not found at the first column of a row: '%s'", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {tokens[0],
|
||||||
isSet(1) ? tokenizeString<std::vector<string>>(tokens[1], ",") : std::vector<string>{settings.thisSystem},
|
isSet(1) ? tokenizeString<std::vector<string>>(tokens[1], ",") : std::vector<string>{settings.thisSystem},
|
||||||
isSet(2) ? tokens[2] : "",
|
isSet(2) ? tokens[2] : "",
|
||||||
isSet(3) ? std::stoull(tokens[3]) : 1LL,
|
isSet(3) ? parseUnsignedIntField(3) : 1U,
|
||||||
isSet(4) ? std::stoull(tokens[4]) : 1LL,
|
isSet(4) ? parseUnsignedIntField(4) : 1U,
|
||||||
isSet(5) ? tokenizeString<std::set<string>>(tokens[5], ",") : std::set<string>{},
|
isSet(5) ? tokenizeString<std::set<string>>(tokens[5], ",") : std::set<string>{},
|
||||||
isSet(6) ? tokenizeString<std::set<string>>(tokens[6], ",") : std::set<string>{},
|
isSet(6) ? tokenizeString<std::set<string>>(tokens[6], ",") : std::set<string>{},
|
||||||
isSet(7) ? tokens[7] : "");
|
isSet(7) ? ensureBase64(7) : ""};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Machines parseBuilderLines(const std::vector<std::string>& builders) {
|
||||||
|
Machines result;
|
||||||
|
std::transform(builders.begin(), builders.end(), std::back_inserter(result), parseBuilderLine);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Machines getMachines()
|
Machines getMachines()
|
||||||
{
|
{
|
||||||
static auto machines = [&]() {
|
const auto builderLines = expandBuilderLines(settings.builders);
|
||||||
Machines machines;
|
return parseBuilderLines(builderLines);
|
||||||
parseMachines(settings.builders, machines);
|
|
||||||
return machines;
|
|
||||||
}();
|
|
||||||
return machines;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
169
src/libstore/tests/machines.cc
Normal file
169
src/libstore/tests/machines.cc
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
#include "machines.hh"
|
||||||
|
#include "globals.hh"
|
||||||
|
|
||||||
|
#include <gmock/gmock-matchers.h>
|
||||||
|
|
||||||
|
using testing::Contains;
|
||||||
|
using testing::ElementsAre;
|
||||||
|
using testing::EndsWith;
|
||||||
|
using testing::Eq;
|
||||||
|
using testing::Field;
|
||||||
|
using testing::SizeIs;
|
||||||
|
|
||||||
|
using nix::absPath;
|
||||||
|
using nix::FormatError;
|
||||||
|
using nix::getMachines;
|
||||||
|
using nix::Machine;
|
||||||
|
using nix::Machines;
|
||||||
|
using nix::pathExists;
|
||||||
|
using nix::Settings;
|
||||||
|
using nix::settings;
|
||||||
|
|
||||||
|
class Environment : public ::testing::Environment {
|
||||||
|
public:
|
||||||
|
void SetUp() override { settings.thisSystem = "TEST_ARCH-TEST_OS"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
testing::Environment* const foo_env =
|
||||||
|
testing::AddGlobalTestEnvironment(new Environment);
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithEmptyBuilders) {
|
||||||
|
settings.builders = "";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesUriOnly) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(1));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::storeUri, Eq("ssh://nix@scratchy.labs.cs.uu.nl")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("TEST_ARCH-TEST_OS")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshKey, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(1)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(1)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, SizeIs(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesDefaults) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - - - - - -";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(1));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::storeUri, Eq("ssh://nix@scratchy.labs.cs.uu.nl")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("TEST_ARCH-TEST_OS")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshKey, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(1)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(1)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, SizeIs(0)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, SizeIs(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithNewLineSeparator) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl\nnix@itchy.labs.cs.uu.nl";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(2));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithSemicolonSeparator) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl ; nix@itchy.labs.cs.uu.nl";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
EXPECT_THAT(actual, SizeIs(2));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithCorrectCompleteSingleBuilder) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl i686-linux "
|
||||||
|
"/home/nix/.ssh/id_scratchy_auto 8 3 kvm "
|
||||||
|
"benchmark SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(1));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("i686-linux")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshKey, Eq("/home/nix/.ssh/id_scratchy_auto")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(8)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(3)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("kvm")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("benchmark")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, Eq("SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines,
|
||||||
|
getMachinesWithCorrectCompleteSingleBuilderWithTabColumnDelimiter) {
|
||||||
|
settings.builders =
|
||||||
|
"nix@scratchy.labs.cs.uu.nl\ti686-linux\t/home/nix/.ssh/"
|
||||||
|
"id_scratchy_auto\t8\t3\tkvm\tbenchmark\tSSH+HOST+PUBLIC+"
|
||||||
|
"KEY+BASE64+ENCODED==";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(1));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("i686-linux")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshKey, Eq("/home/nix/.ssh/id_scratchy_auto")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::maxJobs, Eq(8)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::speedFactor, Eq(3)));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("kvm")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("benchmark")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::sshPublicHostKey, Eq("SSH+HOST+PUBLIC+KEY+BASE64+ENCODED==")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithMultiOptions) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl Arch1,Arch2 - - - "
|
||||||
|
"SupportedFeature1,SupportedFeature2 "
|
||||||
|
"MandatoryFeature1,MandatoryFeature2";
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(1));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::systemTypes, ElementsAre("Arch1", "Arch2")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::supportedFeatures, ElementsAre("SupportedFeature1", "SupportedFeature2")));
|
||||||
|
EXPECT_THAT(actual[0], Field(&Machine::mandatoryFeatures, ElementsAre("MandatoryFeature1", "MandatoryFeature2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithIncorrectFormat) {
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - eight";
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - -1";
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 three";
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 -3";
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
settings.builders = "nix@scratchy.labs.cs.uu.nl - - 8 3 - - BAD_BASE64";
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithCorrectFileReference) {
|
||||||
|
auto path = absPath("src/libstore/tests/test-data/machines.valid");
|
||||||
|
ASSERT_TRUE(pathExists(path));
|
||||||
|
|
||||||
|
settings.builders = std::string("@") + path;
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(3));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@scratchy.labs.cs.uu.nl"))));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@itchy.labs.cs.uu.nl"))));
|
||||||
|
EXPECT_THAT(actual, Contains(Field(&Machine::storeUri, EndsWith("nix@poochie.labs.cs.uu.nl"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithCorrectFileReferenceToEmptyFile) {
|
||||||
|
auto path = "/dev/null";
|
||||||
|
ASSERT_TRUE(pathExists(path));
|
||||||
|
|
||||||
|
settings.builders = std::string("@") + path;
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithIncorrectFileReference) {
|
||||||
|
settings.builders = std::string("@") + absPath("/not/a/file");
|
||||||
|
Machines actual = getMachines();
|
||||||
|
ASSERT_THAT(actual, SizeIs(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(machines, getMachinesWithCorrectFileReferenceToIncorrectFile) {
|
||||||
|
settings.builders = std::string("@") + absPath("src/libstore/tests/test-data/machines.bad_format");
|
||||||
|
EXPECT_THROW(getMachines(), FormatError);
|
||||||
|
}
|
1
src/libstore/tests/test-data/machines.bad_format
Normal file
1
src/libstore/tests/test-data/machines.bad_format
Normal file
|
@ -0,0 +1 @@
|
||||||
|
nix@scratchy.labs.cs.uu.nl - - eight
|
3
src/libstore/tests/test-data/machines.valid
Normal file
3
src/libstore/tests/test-data/machines.valid
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvm
|
||||||
|
nix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2
|
||||||
|
nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFDQVFDWWV5R1laNTNzd1VjMUZNSHBWL1BCcXlKaFR5S1JoRkpWWVRpRHlQN2h5c1JGa0w4VDlLOGdhL2Y2L3c3QjN2SjNHSFRIUFkybENiUEdZbGNLd2h6M2ZRbFNNOEViNi95b3ZLajdvM1FsMEx5Y0dzdGJvRmcwWkZKNldncUxsR0ltS0NobUlxOGZ3TW5ZTWUxbnRQeTBUZFZjSU1tOTV3YzF3SjBMd2c3cEVMRmtHazdkeTVvYnM4a3lGZ0pORDVRSmFwQWJjeWp4Z1QzdzdMcktNZ2xzeWhhd01JNVpkMGZsQTVudW5OZ3pid3plYVhLaUsyTW0vdGJXYTU1YTd4QmNYdHpIZGlPSWdSajJlRWxaMGh5bk10YjBmcklsdmxIcEtLaVFaZ3pQdCtIVXQ2bXpRMkRVME52MGYyYnNSU0krOGpJU2pQcmdlcVVHRldMUzVIUTg2N2xSMlpiaWtyclhZNTdqbVFEZk5DRHY1VFBHZU9UekFEd2pjMDc2aFZ3VFJCd3VTZFhtaWNxTS95b3lrWitkV1dnZ25MenE5QU1tdlNZcDhmZkZDcS9CSDBZNUFXWTFHay9vS3hMVTNaOWt3ZDd2UWNFQWFCQ2dxdnVZRGdTaHE1RlhndDM3OVZESWtEL05ZSTg2QXVvajVDRmVNTzlRM2pJSlRadlh6c1VldjVoSnA2djcxSVh5ODVtbTY5R20zcXdicVE1SjVQZDU1Um56SitpaW5BNjZxTEFSc0Y4amNsSnd5ekFXclBoYU9DRVY2bjVMeVhVazhzMW9EVVR4V1pWN25rVkFTbHJ0MllGcjN5dzdjRTRXQVhsemhHcDhocmdLMVVkMUlyeDVnZWRaSnBWcy9uNWVybmJFMUxmb2x5UHUvRUFIWlh6VGd4dHVDUFNobXc9PQo=
|
Loading…
Reference in a new issue