Unverified Commit e2f6e5d7 authored by Philip Taron's avatar Philip Taron Committed by GitHub
Browse files

testers.testEqualArrayOrMap: init (#383214)

parents 3b0cc6e6 674c9073
Loading
Loading
Loading
Loading
+91 −0
Original line number Diff line number Diff line
@@ -347,6 +347,97 @@ testers.testEqualContents {

:::

## `testEqualArrayOrMap` {#tester-testEqualArrayOrMap}

Check that bash arrays (including associative arrays, referred to as "maps") are populated correctly.

This can be used to ensure setup hooks are registered in a certain order, or to write unit tests for shell functions which transform arrays.

:::{.example #ex-testEqualArrayOrMap-test-function-add-cowbell}

# Test a function which appends a value to an array

```nix
testers.testEqualArrayOrMap {
  name = "test-function-add-cowbell";
  valuesArray = [
    "cowbell"
    "cowbell"
  ];
  expectedArray = [
    "cowbell"
    "cowbell"
    "cowbell"
  ];
  script = ''
    addCowbell() {
      local -rn arrayNameRef="$1"
      arrayNameRef+=( "cowbell" )
    }

    nixLog "appending all values in valuesArray to actualArray"
    for value in "''${valuesArray[@]}"; do
      actualArray+=( "$value" )
    done

    nixLog "applying addCowbell"
    addCowbell actualArray
  '';
}
```

:::

### Inputs {#tester-testEqualArrayOrMap-inputs}

NOTE: Internally, this tester uses `__structuredAttrs` to handle marshalling between Nix expressions and shell variables.
This imposes the restriction that arrays and "maps" have values which are string-like.

NOTE: At least one of `expectedArray` and `expectedMap` must be provided.

`name` (string)

: The name of the test.

`script` (string)

: The singular task of `script` is to populate `actualArray` or `actualMap` (it may populate both).
  To do this, `script` may access the following shell variables:

  - `valuesArray` (available when `valuesArray` is provided to the tester)
  - `valuesMap` (available when `valuesMap` is provided to the tester)
  - `actualArray` (available when `expectedArray` is provided to the tester)
  - `actualMap` (available when `expectedMap` is provided to the tester)

  While both `expectedArray` and `expectedMap` are in scope during the execution of `script`, they *must not* be accessed or modified from within `script`.

`valuesArray` (array of string-like values, optional)

: An array of string-like values.
  This array may be used within `script`.

`valuesMap` (attribute set of string-like values, optional)

: An attribute set of string-like values.
  This attribute set may be used within `script`.

`expectedArray` (array of string-like values, optional)

: An array of string-like values.
  This array *must not* be accessed or modified from within `script`.
  When provided, `script` is expected to populate `actualArray`.

`expectedMap` (attribute set of string-like values, optional)

: An attribute set of string-like values.
  This attribute set *must not* be accessed or modified from within `script`.
  When provided, `script` is expected to populate `actualMap`.

### Return value {#tester-testEqualArrayOrMap-return}

The tester produces an empty output and only succeeds when `expectedArray` and `expectedMap` match `actualArray` and `actualMap`, respectively, when non-null.
The build log will contain differences encountered.

## `testEqualDerivation` {#tester-testEqualDerivation}

Checks that two packages produce the exact same build instructions.
+12 −0
Original line number Diff line number Diff line
@@ -11,6 +11,9 @@
  "ex-testBuildFailurePrime-doc-example": [
    "index.html#ex-testBuildFailurePrime-doc-example"
  ],
  "ex-testEqualArrayOrMap-test-function-add-cowbell": [
    "index.html#ex-testEqualArrayOrMap-test-function-add-cowbell"
  ],
  "neovim": [
    "index.html#neovim"
  ],
@@ -344,6 +347,15 @@
  "tester-testBuildFailurePrime-return": [
    "index.html#tester-testBuildFailurePrime-return"
  ],
  "tester-testEqualArrayOrMap": [
    "index.html#tester-testEqualArrayOrMap"
  ],
  "tester-testEqualArrayOrMap-inputs": [
    "index.html#tester-testEqualArrayOrMap-inputs"
  ],
  "tester-testEqualArrayOrMap-return": [
    "index.html#tester-testEqualArrayOrMap-return"
  ],
  "variables-specifying-dependencies": [
    "index.html#variables-specifying-dependencies"
  ],
+4 −0
Original line number Diff line number Diff line
@@ -69,6 +69,10 @@
    fi
  '';

  # See https://nixos.org/manual/nixpkgs/unstable/#tester-testEqualArrayOrMap
  # or doc/build-helpers/testers.chapter.md
  testEqualArrayOrMap = callPackage ./testEqualArrayOrMap { };

  # See https://nixos.org/manual/nixpkgs/unstable/#tester-testVersion
  # or doc/build-helpers/testers.chapter.md
  testVersion =
+2 −0
Original line number Diff line number Diff line
@@ -356,4 +356,6 @@ lib.recurseIntoAttrs {
        touch -- "$out"
      '';
  };

  testEqualArrayOrMap = pkgs.callPackages ../testEqualArrayOrMap/tests.nix { };
}
+71 −0
Original line number Diff line number Diff line
# shellcheck shell=bash

# Tests if an array is declared.
isDeclaredArray() {
  # shellcheck disable=SC2034
  local -nr arrayRef="$1" && [[ ${!arrayRef@a} =~ a ]]
}

# Asserts that two arrays are equal, printing out differences if they are not.
# Does not short circuit on the first difference.
assertEqualArray() {
  if (($# != 2)); then
    nixErrorLog "expected two arguments!"
    nixErrorLog "usage: assertEqualArray expectedArrayRef actualArrayRef"
    exit 1
  fi

  local -nr expectedArrayRef="$1"
  local -nr actualArrayRef="$2"

  if ! isDeclaredArray "${!expectedArrayRef}"; then
    nixErrorLog "first arugment expectedArrayRef must be an array reference to a declared array"
    exit 1
  fi

  if ! isDeclaredArray "${!actualArrayRef}"; then
    nixErrorLog "second arugment actualArrayRef must be an array reference to a declared array"
    exit 1
  fi

  local -ir expectedLength=${#expectedArrayRef[@]}
  local -ir actualLength=${#actualArrayRef[@]}

  local -i hasDiff=0

  if ((expectedLength != actualLength)); then
    nixErrorLog "arrays differ in length: expectedArray has length $expectedLength but actualArray has length $actualLength"
    hasDiff=1
  fi

  local -i idx=0
  local expectedValue
  local actualValue

  # We iterate so long as at least one array has indices we've not considered.
  # This means that `idx` is a valid index to *at least one* of the arrays.
  for ((idx = 0; idx < expectedLength || idx < actualLength; idx++)); do
    # Update values for variables which are still in range/valid.
    if ((idx < expectedLength)); then
      expectedValue="${expectedArrayRef[idx]}"
    fi

    if ((idx < actualLength)); then
      actualValue="${actualArrayRef[idx]}"
    fi

    # Handle comparisons.
    if ((idx >= expectedLength)); then
      nixErrorLog "arrays differ at index $idx: expectedArray has no such index but actualArray has value ${actualValue@Q}"
      hasDiff=1
    elif ((idx >= actualLength)); then
      nixErrorLog "arrays differ at index $idx: expectedArray has value ${expectedValue@Q} but actualArray has no such index"
      hasDiff=1
    elif [[ $expectedValue != "$actualValue" ]]; then
      nixErrorLog "arrays differ at index $idx: expectedArray has value ${expectedValue@Q} but actualArray has value ${actualValue@Q}"
      hasDiff=1
    fi
  done

  ((hasDiff)) && exit 1 || return 0
}
Loading