expectFailure 'toSource { root = "/nix/store/foobar"; fileset = ./.; }''lib.fileset.toSource: `root`\(/nix/store/foobar\) is a string-like value, but it should be a path instead.
expectFailure 'toSource { root = cleanSourceWith { src = ./.; }; fileset = ./.; }''lib.fileset.toSource: `root` is a `lib.sources`-based value, but it should be a path instead.
\s*To use a `lib.sources`-based value, convert it to a file set using `lib.fileset.fromSource` and pass it as `fileset`.
\s*Note that this only works for sources created from paths.'
expectFailure 'toSource { root = ./a; fileset = ./a; }''lib.fileset.toSource: `root` \('"$work"'/a\) is a file, but it should be a directory instead. Potential solutions:
\s*- If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function.
\s*- If you want to import the file into the store _with_ a containing directory, set`root` to the containing directory, such as '"$work"', and set`fileset` to the file path.'
# The fileset argument should be evaluated, even if the directory is empty
expectFailure 'toSource { root = ./.; fileset = abort "This should be evaluated"; }''evaluation aborted with the following error message: '\''This should be evaluated'\'
# Only paths under `root` should be able to influence the result
mkdir a
expectFailure 'toSource { root = ./a; fileset = ./.; }''lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` \('"$work"'/a\). Potential solutions:
\s*- Set `root` to '"$work"' or any directory higher up. This changes the layout of the resulting store path.
\s*- Set `fileset` to a file set that cannot contain files outside the `root`\('"$work"'/a\). This could change the files included in the result.'
expectFailure 'toSource { root = ./.; fileset = 10; }''lib.fileset.toSource: `fileset` is of type int, but it should be a file set or a path instead.'
expectFailure 'toSource { root = ./.; fileset = "/some/path"; }''lib.fileset.toSource: `fileset`\("/some/path"\) is a string-like value, but it should be a file set or a path instead.
expectFailure 'toSource { root = ./.; fileset = cleanSourceWith { src = ./.; }; }''lib.fileset.toSource: `fileset` is a `lib.sources`-based value, but it should be a file set or a path instead.
\s*To convert a `lib.sources`-based value to a file set you can use `lib.fileset.fromSource`.
\s*Note that this only works for sources created from paths.'
# Future versions of the internal representation are unsupported
expectFailure '_coerce "<tests>: value" { _type = "fileset"; _internalVersion = 4; }''<tests>: value is a file set created from a future version of the file set library with a different internal representation:
\s*- Internal version of the file set: 4
\s*- Internal version of the library: 3
\s*Make sure to update your Nixpkgs to have a newer version of `lib.fileset`.'
# _create followed by _coerce should give the inputs back without any validation
# We should be able to import an empty directory and end up with an empty result
tree=(
)
checkFileset './.'
# The empty value without a base should also result in an empty result
tree=(
[a]=0
)
checkFileset '_emptyWithoutBase'
# Directories recursively containing no files are not included
tree=(
[e/]=0
[d/e/]=0
[d/d/e/]=0
[d/d/f]=1
[d/f]=1
[f]=1
)
checkFileset './.'
# Check trees that could cause a naïve string prefix checking implementation to fail
tree=(
[a]=0
[ab/x]=0
[ab/xy]=1
[ab/xyz]=0
[abc]=0
)
checkFileset './ab/xy'
# Check path coercion examples in ../../doc/functions/fileset.section.md
tree=(
[a/x]=1
[a/b/y]=1
[c/]=0
[c/d/]=0
)
checkFileset './.'
tree=(
[a/x]=1
[a/b/y]=1
[c/]=0
[c/d/]=0
)
checkFileset './a'
tree=(
[a/x]=1
[a/b/y]=0
[c/]=0
[c/d/]=0
)
checkFileset './a/x'
tree=(
[a/x]=0
[a/b/y]=1
[c/]=0
[c/d/]=0
)
checkFileset './a/b'
tree=(
[a/x]=0
[a/b/y]=0
[c/]=0
[c/d/]=0
)
checkFileset './c'
# Test the source filter for the somewhat special case of files in the filesystem root
# We can't easily test this with the above functions because we can't write to the filesystem root and we don't want to make any assumptions which files are there in the sandbox
expectFailure 'toSource { root = ./.; fileset = intersection ./a ./.; }''lib.fileset.intersection: First argument \('"$work"'/a\) is a path that does not exist.'
expectFailure 'toSource { root = ./.; fileset = intersection ./. ./b; }''lib.fileset.intersection: Second argument \('"$work"'/b\) is a path that does not exist.'
expectFailure 'toSource { root = ./a; fileset = difference ./. ./a; }''lib.fileset.toSource: `fileset` could contain files in '"$work"', which is not under the `root` \('"$work"'/a\). Potential solutions:
\s*- Set `root` to '"$work"' or any directory higher up. This changes the layout of the resulting store path.
\s*- Set `fileset` to a file set that cannot contain files outside the `root`\('"$work"'/a\). This could change the files included in the result.'
rm -rf -- *
# Difference actually works
# We test all combinations of ./., ./a, ./a/x and ./b
tree=(
[a/x]=0
[a/y]=0
[b]=0
[c]=0
)
checkFileset 'difference ./. ./.'
checkFileset 'difference ./a ./.'
checkFileset 'difference ./a/x ./.'
checkFileset 'difference ./b ./.'
checkFileset 'difference ./a ./a'
checkFileset 'difference ./a/x ./a'
checkFileset 'difference ./a/x ./a/x'
checkFileset 'difference ./b ./b'
tree=(
[a/x]=0
[a/y]=0
[b]=1
[c]=1
)
checkFileset 'difference ./. ./a'
tree=(
[a/x]=1
[a/y]=1
[b]=0
[c]=0
)
checkFileset 'difference ./a ./b'
tree=(
[a/x]=1
[a/y]=0
[b]=0
[c]=0
)
checkFileset 'difference ./a/x ./b'
tree=(
[a/x]=0
[a/y]=1
[b]=0
[c]=0
)
checkFileset 'difference ./a ./a/x'
tree=(
[a/x]=0
[a/y]=0
[b]=1
[c]=0
)
checkFileset 'difference ./b ./a'
checkFileset 'difference ./b ./a/x'
tree=(
[a/x]=0
[a/y]=1
[b]=1
[c]=1
)
checkFileset 'difference ./. ./a/x'
tree=(
[a/x]=1
[a/y]=1
[b]=0
[c]=1
)
checkFileset 'difference ./. ./b'
## File filter
# The first argument needs to be a function
expectFailure 'fileFilter null (abort "this is not needed")''lib.fileset.fileFilter: First argument is of type null, but it should be a function instead.'
# The second argument needs to be an existing path
expectFailure 'fileFilter (file: abort "this is not needed") _emptyWithoutBase''lib.fileset.fileFilter: Second argument is a file set, but it should be a path instead.
\s*If you need to filter files in a file set, use `intersection fileset \(fileFilter pred \./\.\)` instead.'
expectFailure 'fileFilter (file: abort "this is not needed") null''lib.fileset.fileFilter: Second argument is of type null, but it should be a path instead.'
expectFailure 'fileFilter (file: abort "this is not needed") ./a''lib.fileset.fileFilter: Second argument \('"$work"'/a\) is a path that does not exist.'
# The predicate is not called when there's no files
tree=()
checkFileset 'fileFilter (file: abort "this is not needed") ./.'
# The predicate must be able to handle extra attributes
expectFailure 'toSource { root = ./.; fileset = fileFilter ({ name, type, hasExt }: true) ./.; }''called with unexpected argument '\''"lib.fileset.fileFilter: The predicate function passed as the first argument must be able to handle extra attributes for future compatibility. If you'\''re using `\{ name, file, hasExt \}:`, use `\{ name, file, hasExt, ... \}:` instead."'\'
expectFailure 'fromSource (lib.cleanSource "")''lib.fileset.fromSource: The source origin of the argument is a string-like value \(""\), but it should be a path instead.
\s*Sources created from paths in strings cannot be turned into file sets, use `lib.sources` or derivations instead.'
expectFailure 'fromSource (lib.cleanSource null)''lib.fileset.fromSource: The source origin of the argument is of type null, but it should be a path instead.'
# If it's not in the source store path, it's also not in the file set store path
if[[ -e "$filesetStorePath"/"$subpath"]];then
die "The store path $sourceStorePath created by $expr doesn't contain $subpath, but the corresponding store path $filesetStorePath created via fromSource does contain $subpath"
# If it's an empty directory in the source store path, it shouldn't be in the file set store path
if[[ -e "$filesetStorePath"/"$subpath"]];then
die "The store path $sourceStorePath created by $expr contains the path $subpath without any files, but the corresponding store path $filesetStorePath created via fromSource didn't omit it"
fi
else
# If it's non-empty directory or a file, it should be in the file set store path
if[[ ! -e "$filesetStorePath"/"$subpath"]];then
die "The store path $sourceStorePath created by $expr contains the non-empty path $subpath, but the corresponding store path $filesetStorePath created via fromSource doesn't include it"
fi
fi
done < <(find . -mindepth 1 -print0)
rm -rf -- *
}
# Check whether the filter is evaluated correctly
tree=(
[a]=
[b/]=
[b/c]=
[b/d]=
[e/]=
[e/e/]=
)
# We fill out the above tree values with all possible combinations of 0 and 1
# Then check whether a filter based on those return values gets turned into the corresponding file set
for i in $(seq 0$((2**${#tree[@]}-1)));do
for p in "${!tree[@]}";do
tree[$p]=$(( i %2))
(( i /=2))||true
done
checkSource
done
# The filter is called with the same arguments in the same order
expectFailure 'gitTracked ./a''lib.fileset.gitTracked: Expected the argument \('"$work"'/a\) to be a directory, but it'\''s a file instead'
expectFailure 'gitTrackedWith {} ./a''lib.fileset.gitTrackedWith: Expected the second argument \('"$work"'/a\) to be a directory, but it'\''s a file instead'
expectFailure 'gitTracked ./.''lib.fileset.gitTracked: Expected the argument \('"$work"'\) to point to a local working tree of a Git repository, but it'\''s not.'
expectFailure 'gitTrackedWith {} ./.''lib.fileset.gitTrackedWith: Expected the second argument \('"$work"'\) to point to a local working tree of a Git repository, but it'\''s not.'
# recurseSubmodules has to be a boolean
expectFailure 'gitTrackedWith { recurseSubmodules = null; } ./.''lib.fileset.gitTrackedWith: Expected the attribute `recurseSubmodules` of the first argument to be a boolean, but it'\''s a null instead.'
# recurseSubmodules = true is not supported on all Nix versions
expectFailure 'gitTrackedWith { recurseSubmodules = true; } ./.''lib.fileset.gitTrackedWith: Setting the attribute `recurseSubmodules` to `true` is only supported for Nix version 2.4 and after, but Nix version [0-9.]+ is used.'
fi
# Checks that `gitTrackedWith` contains the same files as `git ls-files`
# for the current working directory.
# If --recurse-submodules is passed, the flag is passed through to `git ls-files`
# and as `recurseSubmodules` to `gitTrackedWith`
checkGitTrackedWith(){
if[["${1:-}"=="--recurse-submodules"]];then
gitLsFlags="--recurse-submodules"
gitTrackedArg="{ recurseSubmodules = true; }"
else
gitLsFlags=""
gitTrackedArg="{ }"
fi
# All files listed by `git ls-files`
expectedFiles=()
whileIFS=read -r -d $'\0' file;do
# If there are submodules but --recurse-submodules isn't passed,
# `git ls-files` lists them as empty directories,
# we need to filter that out since we only want to check/count files
# Check that each expected file is also in the store path with the same content
for expectedFile in "${expectedFiles[@]}";do
if[[ ! -e "$storePath"/"$expectedFile"]];then
die "Expected file $expectedFile to exist in $storePath, but it doesn't.\nGit status:\n$(git status)\nStore path contents:\n$(find "$storePath")"
fi
if ! diff "$expectedFile""$storePath"/"$expectedFile";then
die "Expected file $expectedFile to have the same contents as in $storePath, but it doesn't.\nGit status:\n$(git status)\nStore path contents:\n$(find "$storePath")"
fi
done
# This is a cheap way to verify the inverse: That all files in the store path are also expected
# We just count the number of files in both and verify they're the same
actualFileCount=$(find "$storePath" -type f -printf . | wc -c)
die "Expected ${#expectedFiles[@]} files in $storePath, but got $actualFileCount.\nGit status:\n$(git status)\nStore path contents:\n$(find "$storePath")"
fi
}
# Runs checkGitTrackedWith with and without --recurse-submodules
# Allows testing both variants together
checkGitTracked(){
checkGitTrackedWith
if[[ -n "$fetchGitSupportsSubmodules"]];then
checkGitTrackedWith --recurse-submodules
fi
}
createGitRepo(){
git init -q "$1"
# Only repo-local config
git -C "$1" config user.name "Nixpkgs"
git -C "$1" config user.email "nixpkgs@nixos.org"
# Get at least a HEAD commit, needed for older Nix versions
## But it fails if the path is imported with a fetcher that doesn't remove .git (like just using "${./.}")
expectFailure 'import "${./.}" { fs = lib.fileset; }''lib.fileset.gitTracked: The argument \(.*\) is a store path within a working tree of a Git repository.
\s*This indicates that a source directory was imported into the store using a method such as `import "\$\{./.\}"` or `path:.`.
\s*This function currently does not support such a use case, since it currently relies on `builtins.fetchGit`.
\s*You could make this work by using a fetcher such as `fetchGit` instead of copying the whole repository.
\s*If you can'\''t avoid copying the repo to the store, see https://github.com/NixOS/nix/issues/9292.'
## But it fails if the path is imported with a fetcher that doesn't remove .git (like just using "${./.}")
expectFailure 'import "${./.}" { fs = lib.fileset; }''lib.fileset.gitTrackedWith: The second argument \(.*\) is a store path within a working tree of a Git repository.
\s*This indicates that a source directory was imported into the store using a method such as `import "\$\{./.\}"` or `path:.`.
\s*This function currently does not support such a use case, since it currently relies on `builtins.fetchGit`.
\s*You could make this work by using a fetcher such as `fetchGit` instead of copying the whole repository.
\s*If you can'\''t avoid copying the repo to the store, see https://github.com/NixOS/nix/issues/9292.'
expectFailure 'import "${./.}/sub" { fs = lib.fileset; }''lib.fileset.gitTracked: The argument \(.*/sub\) is a store path within a working tree of a Git repository.
\s*This indicates that a source directory was imported into the store using a method such as `import "\$\{./.\}"` or `path:.`.
\s*This function currently does not support such a use case, since it currently relies on `builtins.fetchGit`.
\s*You could make this work by using a fetcher such as `fetchGit` instead of copying the whole repository.
\s*If you can'\''t avoid copying the repo to the store, see https://github.com/NixOS/nix/issues/9292.'
expectFailure 'maybeMissing "someString"''lib.fileset.maybeMissing: Argument \("someString"\) is a string-like value, but it should be a path instead.'
expectFailure 'maybeMissing null''lib.fileset.maybeMissing: Argument is of type null, but it should be a path instead.'