Skip to content

Commit

Permalink
Merge pull request #237512 from hercules-ci/lib-system-equals
Browse files Browse the repository at this point in the history
  • Loading branch information
Artturin authored Jun 15, 2023
2 parents 95c729c + 3150f25 commit 5ff6f51
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 7 deletions.
33 changes: 33 additions & 0 deletions lib/systems/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ rec {
examples = import ./examples.nix { inherit lib; };
architectures = import ./architectures.nix { inherit lib; };

/*
Elaborated systems contain functions, which means that they don't satisfy
`==` for a lack of reflexivity.
They might *appear* to satisfy `==` reflexivity when the same exact value is
compared to itself, because object identity is used as an "optimization";
compare the value with a reconstruction of itself, e.g. with `f == a: f a`,
or perhaps calling `elaborate` twice, and one will see reflexivity fail as described.
Hence a custom equality test.
Note that this does not canonicalize the systems, so you'll want to make sure
both arguments have been `elaborate`-d.
*/
equals =
let removeFunctions = a: lib.filterAttrs (_: v: !builtins.isFunction v) a;
in a: b: removeFunctions a == removeFunctions b;

/*
Try to convert an elaborated system back to a simple string. If not possible,
return null. So we have the property:
sys: _valid_ sys ->
sys == elaborate (toLosslessStringMaybe sys)
NOTE: This property is not guaranteed when `sys` was elaborated by a different
version of Nixpkgs.
*/
toLosslessStringMaybe = sys:
if lib.isString sys then sys
else if equals sys (elaborate sys.system) then sys.system
else null;

/* List of all Nix system doubles the nixpkgs flake will expose the package set
for. All systems listed here must be supported by nixpkgs as `localSystem`.
Expand Down
3 changes: 3 additions & 0 deletions lib/tests/release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ let
echo "Running lib/tests/sources.sh"
TEST_LIB=$PWD/lib bash lib/tests/sources.sh
echo "Running lib/tests/systems.nix"
[[ $(nix-instantiate --eval --strict lib/tests/systems.nix | tee /dev/stderr) == '[ ]' ]];
mkdir $out
echo success > $out/${nix.version}
'';
Expand Down
61 changes: 54 additions & 7 deletions lib/tests/systems.nix
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
# We assert that the new algorithmic way of generating these lists matches the
# way they were hard-coded before.
# Run:
# [nixpkgs]$ nix-instantiate --eval --strict lib/tests/systems.nix
# Expected output: [], or the failed cases
#
# One might think "if we exhaustively test, what's the point of procedurally
# calculating the lists anyway?". The answer is one can mindlessly update these
# tests as new platforms become supported, and then just give the diff a quick
# sanity check before committing :).
# OfBorg runs (approximately) nix-build lib/tests/release.nix
let
lib = import ../default.nix;
mseteq = x: y: {
expr = lib.sort lib.lessThan x;
expected = lib.sort lib.lessThan y;
};
in
with lib.systems.doubles; lib.runTests {
lib.runTests (
# We assert that the new algorithmic way of generating these lists matches the
# way they were hard-coded before.
#
# One might think "if we exhaustively test, what's the point of procedurally
# calculating the lists anyway?". The answer is one can mindlessly update these
# tests as new platforms become supported, and then just give the diff a quick
# sanity check before committing :).

(with lib.systems.doubles; {
testall = mseteq all (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ wasi ++ windows ++ embedded ++ mmix ++ js ++ genode ++ redox);

testarm = mseteq arm [ "armv5tel-linux" "armv6l-linux" "armv6l-netbsd" "armv6l-none" "armv7a-linux" "armv7a-netbsd" "armv7l-linux" "armv7l-netbsd" "arm-none" "armv7a-darwin" ];
Expand All @@ -39,4 +46,44 @@ with lib.systems.doubles; lib.runTests {
testopenbsd = mseteq openbsd [ "i686-openbsd" "x86_64-openbsd" ];
testwindows = mseteq windows [ "i686-cygwin" "x86_64-cygwin" "i686-windows" "x86_64-windows" ];
testunix = mseteq unix (linux ++ darwin ++ freebsd ++ openbsd ++ netbsd ++ illumos ++ cygwin ++ redox);
})

// {
test_equals_example_x86_64-linux = {
expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") (lib.systems.elaborate "x86_64-linux");
expected = true;
};

test_toLosslessStringMaybe_example_x86_64-linux = {
expr = lib.systems.toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux");
expected = "x86_64-linux";
};
test_toLosslessStringMaybe_fail = {
expr = lib.systems.toLosslessStringMaybe (lib.systems.elaborate "x86_64-linux" // { something = "extra"; });
expected = null;
};
}

# Generate test cases to assert that a change in any non-function attribute makes a platform unequal
// lib.concatMapAttrs (platformAttrName: origValue: {

${"test_equals_unequal_${platformAttrName}"} =
let modified =
assert origValue != arbitraryValue;
lib.systems.elaborate "x86_64-linux" // { ${platformAttrName} = arbitraryValue; };
arbitraryValue = x: "<<modified>>";
in {
expr = lib.systems.equals (lib.systems.elaborate "x86_64-linux") modified;
expected = {
# Changes in these attrs are not detectable because they're function.
# The functions should be derived from the data, so this is not a problem.
canExecute = null;
emulator = null;
emulatorAvailable = null;
isCompatible = null;
}?${platformAttrName};
};

}) (lib.systems.elaborate "x86_64-linux" /* arbitrary choice, just to get all the elaborated attrNames */)

)

0 comments on commit 5ff6f51

Please sign in to comment.