检查目录是否包含所有(且仅)列出的文件
check whether a directory contains all (and only) the listed files
我正在编写单元测试来测试文件 IO 功能。
在我的目标语言中没有正式的测试框架,所以我的想法是 运行 一个小测试程序,以某种方式操作测试目录中的文件,然后在一个小 shell 脚本中检查结果.
为了评估输出,我想检查给定目录是否存在所有预期文件,并且在测试期间是否未创建其他文件。
我的第一次尝试是这样的:
set -e
test -e "${test_dir}/a.txt"
test -e "${test_dir}/b.txt"
test -d "${test_dir}/dir"
find "${test_dir}" -mindepth 1 \
-not -wholename "${test_dir}/a.txt" \
-not -wholename "${test_dir}/b.txt" \
-not -wholename "${test_dir}/dir" \
| grep . && exit 1 || true
这正确检测是否有两个文件 a.txt
和 b.txt
,以及 ${test_dir}
中的子目录 dir/
。
如果碰巧有一个文件 c.txt
,测试应该并且将会失败。
但是,这并不能很好地扩展。
有几十个单元测试,每个都有一组不同的files/directories,所以我发现自己一次又一次地重复与上面非常相似的行。
所以我宁愿将上面的内容包装成这样的函数调用:
if checkdirectory "${test_dir}" a.txt b.txt dir/ dir/subdir/ dir/.hidden.txt; then
echo "ok"
else
echo "ko"
fi
不幸的是,我不知道如何实现 checkdirectory
(尤其是 find
调用多个 -not -wholename ...
节让我很头疼)。
为了增加一点乐趣,限制条件是:
- 同时支持(并区分)文件和目录
- 必须(编辑自应该)运行 Linux、macOS & MSYS2/MinGW , 因此:
- POSIX 如果可能(实际上它将是
bash
,但可能是 bash<<4!所以没有花哨的功能)
编辑
更多限制条件(这些并没有成为我的深夜问题的原始问题;因此只需将它们视为“奖励积分的额外限制条件”)
- 测试目录可能包含子目录和子目录中的文件(最多任意深度),因此任何检查都需要对不仅仅是顶级目录进行操作
- 理想情况下,路径可能包含奇怪的字符,如空格、换行符...(这实际上是单元测试。我们确实想测试这种情况)
testdir
通常是一些使用 mktemp -d
随机生成的目录,所以如果我们能避免在测试中对它进行硬编码就好了
- 无法对底层文件系统做出任何假设。
我不知道你所说的区分文件和目录是什么意思,因为你最后的 if
语句不知何故是二进制的。以下是对我有用的方法:
#! /bin/bash
function checkdirectory()
{
test_dir=""
shift
content="$@"
for file in ${content}
do
[[ -z "${test_dir}/${file}" ]] && return 1
done
# -I is meant to be appended to "ls" to ignore the files in order to check if other files exist.
matched=" -I ${content// / -I } -I ${test_dir}"
[[ -e `ls $matched` ]] && return 1
return 0
}
if checkdirectory /some/directory a.txt b.txt dir; then
echo "ok"
else
echo "ko"
fi
一种简单快捷的方法是将 find
的输出与参考字符串进行比较:
让我们从预期的目录和文件结构开始:
d/FolderA/filexx.csv
d/FolderA/filexx.doc
d/FolderA/Sub1
d/FolderA/Sub2
testassert
#!/usr/bin/env bash
assertDirContent() {
read -r -d '' s < <(find "" -printf '%y %p\n')
[ "" = "$s" ]
}
testref='d d/FolderA/
f d/FolderA/filexx.csv
f d/FolderA/filexx.doc
d d/FolderA/Sub1
d d/FolderA/Sub2'
if assertDirContent 'd/FolderA/' "$testref"; then
echo 'ok'
else
echo 'Directory content assertion failed'
fi
正在测试:
$ ./testassert
ok
$ touch d/FolderA/unwantedfile
$ ./testassert
Directory content assertion failed
$ rm d/FolderA/unwantedfile
$ ./testassert
ok
$ rmdir d/FolderA/Sub1
$ ./testassert
Directory content assertion failed
$ mkdir d/FolderA/Sub1
$ ./testassert
ok
$ rmdir d/FolderA/Sub2
# Replace with a file instead of a directory
touch d/FolderA/Sub2
$ ./testassert
Directory content assertion failed
现在,如果您将时间戳和权限、所有者、组等其他信息添加到 find -printf
输出,您还可以检查所有这些是否与断言的字符串输出相匹配。
假设我们有一个目录树作为例子:
$test_dir/a.txt
$test_dir/b.txt
$test_dir/dir/c.txt
$test_dir/dir/"d e".txt
$test_dir/dir/subdir/
那你试试看:
#!/bin/sh
checkdirectory() {
local i
local count
local testdir=
shift
for i in "$@"; do
case "$i" in
*/) [ -d "$testdir/$i" ] || return 1 ;; # check if the directory exists
*) [ -f "$testdir/$i" ] || return 1 ;; # check if the file exists
esac
done
# convert each filename to just a newline, then count the lines
count=`find "$testdir" -mindepth 1 -printf "\n" | wc -l`
[ "$count" -eq "$#" ] || return 1
return 0
}
if checkdirectory "$test_dir" a.txt b.txt dir/ dir/c.txt "dir/d e.txt" dir/subdir/; then
echo "ok"
else
echo "ko"
fi
这是我昨晚想到的一个可能的解决方案。
它会破坏测试数据,因此在很多情况下可能无法使用(尽管它可能只适用于单元测试期间即时生成的路径):
checkdirectory() {
local i
local testdir=
shift
# try to remove all the listed files
for i in "$@"; do
if [ "x${i}" = "x${i%/}" ]; then
rm "${testdir}/${i}" || return 1
fi
done
# the directories should now be empty,
# so try to remove those dirs that are listed
for i in "$@"; do
if [ "x${i}" != "x${i%/}" ]; then
rmdir "${testdir}/${i}" || return 1
fi
done
# finally ensure that no files are left
if find "${testdir}" -mindepth 1 | grep . >/dev/null ; then
return 1
fi
return 0
}
调用checkdirectory
函数时,更深层次的目录必须在前(即checkdirectory foo/bar/ foo/
而不是checkdirectory foo/ foo/bar/
)。
我正在编写单元测试来测试文件 IO 功能。 在我的目标语言中没有正式的测试框架,所以我的想法是 运行 一个小测试程序,以某种方式操作测试目录中的文件,然后在一个小 shell 脚本中检查结果.
为了评估输出,我想检查给定目录是否存在所有预期文件,并且在测试期间是否未创建其他文件。
我的第一次尝试是这样的:
set -e
test -e "${test_dir}/a.txt"
test -e "${test_dir}/b.txt"
test -d "${test_dir}/dir"
find "${test_dir}" -mindepth 1 \
-not -wholename "${test_dir}/a.txt" \
-not -wholename "${test_dir}/b.txt" \
-not -wholename "${test_dir}/dir" \
| grep . && exit 1 || true
这正确检测是否有两个文件 a.txt
和 b.txt
,以及 ${test_dir}
中的子目录 dir/
。
如果碰巧有一个文件 c.txt
,测试应该并且将会失败。
但是,这并不能很好地扩展。 有几十个单元测试,每个都有一组不同的files/directories,所以我发现自己一次又一次地重复与上面非常相似的行。
所以我宁愿将上面的内容包装成这样的函数调用:
if checkdirectory "${test_dir}" a.txt b.txt dir/ dir/subdir/ dir/.hidden.txt; then
echo "ok"
else
echo "ko"
fi
不幸的是,我不知道如何实现 checkdirectory
(尤其是 find
调用多个 -not -wholename ...
节让我很头疼)。
为了增加一点乐趣,限制条件是:
- 同时支持(并区分)文件和目录
- 必须(编辑自应该)运行 Linux、macOS & MSYS2/MinGW , 因此:
- POSIX 如果可能(实际上它将是
bash
,但可能是 bash<<4!所以没有花哨的功能)
编辑
更多限制条件(这些并没有成为我的深夜问题的原始问题;因此只需将它们视为“奖励积分的额外限制条件”)
- 测试目录可能包含子目录和子目录中的文件(最多任意深度),因此任何检查都需要对不仅仅是顶级目录进行操作
- 理想情况下,路径可能包含奇怪的字符,如空格、换行符...(这实际上是单元测试。我们确实想测试这种情况)
testdir
通常是一些使用mktemp -d
随机生成的目录,所以如果我们能避免在测试中对它进行硬编码就好了- 无法对底层文件系统做出任何假设。
我不知道你所说的区分文件和目录是什么意思,因为你最后的 if
语句不知何故是二进制的。以下是对我有用的方法:
#! /bin/bash
function checkdirectory()
{
test_dir=""
shift
content="$@"
for file in ${content}
do
[[ -z "${test_dir}/${file}" ]] && return 1
done
# -I is meant to be appended to "ls" to ignore the files in order to check if other files exist.
matched=" -I ${content// / -I } -I ${test_dir}"
[[ -e `ls $matched` ]] && return 1
return 0
}
if checkdirectory /some/directory a.txt b.txt dir; then
echo "ok"
else
echo "ko"
fi
一种简单快捷的方法是将 find
的输出与参考字符串进行比较:
让我们从预期的目录和文件结构开始:
d/FolderA/filexx.csv
d/FolderA/filexx.doc
d/FolderA/Sub1
d/FolderA/Sub2
testassert
#!/usr/bin/env bash
assertDirContent() {
read -r -d '' s < <(find "" -printf '%y %p\n')
[ "" = "$s" ]
}
testref='d d/FolderA/
f d/FolderA/filexx.csv
f d/FolderA/filexx.doc
d d/FolderA/Sub1
d d/FolderA/Sub2'
if assertDirContent 'd/FolderA/' "$testref"; then
echo 'ok'
else
echo 'Directory content assertion failed'
fi
正在测试:
$ ./testassert
ok
$ touch d/FolderA/unwantedfile
$ ./testassert
Directory content assertion failed
$ rm d/FolderA/unwantedfile
$ ./testassert
ok
$ rmdir d/FolderA/Sub1
$ ./testassert
Directory content assertion failed
$ mkdir d/FolderA/Sub1
$ ./testassert
ok
$ rmdir d/FolderA/Sub2
# Replace with a file instead of a directory
touch d/FolderA/Sub2
$ ./testassert
Directory content assertion failed
现在,如果您将时间戳和权限、所有者、组等其他信息添加到 find -printf
输出,您还可以检查所有这些是否与断言的字符串输出相匹配。
假设我们有一个目录树作为例子:
$test_dir/a.txt
$test_dir/b.txt
$test_dir/dir/c.txt
$test_dir/dir/"d e".txt
$test_dir/dir/subdir/
那你试试看:
#!/bin/sh
checkdirectory() {
local i
local count
local testdir=
shift
for i in "$@"; do
case "$i" in
*/) [ -d "$testdir/$i" ] || return 1 ;; # check if the directory exists
*) [ -f "$testdir/$i" ] || return 1 ;; # check if the file exists
esac
done
# convert each filename to just a newline, then count the lines
count=`find "$testdir" -mindepth 1 -printf "\n" | wc -l`
[ "$count" -eq "$#" ] || return 1
return 0
}
if checkdirectory "$test_dir" a.txt b.txt dir/ dir/c.txt "dir/d e.txt" dir/subdir/; then
echo "ok"
else
echo "ko"
fi
这是我昨晚想到的一个可能的解决方案。
它会破坏测试数据,因此在很多情况下可能无法使用(尽管它可能只适用于单元测试期间即时生成的路径):
checkdirectory() {
local i
local testdir=
shift
# try to remove all the listed files
for i in "$@"; do
if [ "x${i}" = "x${i%/}" ]; then
rm "${testdir}/${i}" || return 1
fi
done
# the directories should now be empty,
# so try to remove those dirs that are listed
for i in "$@"; do
if [ "x${i}" != "x${i%/}" ]; then
rmdir "${testdir}/${i}" || return 1
fi
done
# finally ensure that no files are left
if find "${testdir}" -mindepth 1 | grep . >/dev/null ; then
return 1
fi
return 0
}
调用checkdirectory
函数时,更深层次的目录必须在前(即checkdirectory foo/bar/ foo/
而不是)。checkdirectory foo/ foo/bar/