Move tests to pytest

This commit is contained in:
Łukasz Langa 2023-08-02 11:58:50 +02:00
parent 7f9a2e2efc
commit 929fb39782
No known key found for this signature in database
GPG Key ID: B26995E310250568
4 changed files with 361 additions and 214 deletions

View File

@ -49,8 +49,28 @@ Tests
-----
There's a simple but comprehensive test scenario using
`BATS <https://github.com/sstephenson/bats>`. Run the
file in the `tests` directory to run it.
`pytest <https://pypi.org/p/pytest>`_ and
`pytest-order <https://pypi.org/p/pytest-order>`.
Install::
$ python3 -m venv .venv
$ . .venv/bin/activate
(.venv)$ pip install -e .
(.venv)$ pip install -r tests/test_requirements.txt
Run::
(.venv)$ pytest -x
==================== test session starts ====================
platform darwin -- Python 3.10.12, pytest-7.4.0, pluggy-1.2.0
rootdir: /Users/ambv/Documents/Python/bitrot
plugins: order-1.1.0
collected 12 items
tests/test_bitrot.py ............ [100%]
==================== 12 passed in 15.05s ====================
Change Log
----------

View File

@ -1,212 +0,0 @@
#!/usr/bin/env bats
LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
cmd='python -m bitrot'
test_dir=/tmp/bitrot_dir-$USER
mkdir -p $test_dir
cd $test_dir || exit
@test "bitrot command exists" {
run $cmd --help
[ "$status" -eq 0 ]
}
@test "bitrot detects new files in a tree dir" {
mkdir -p nonemptydirs/dir2/
touch nonemptydirs/dir2/new-file-{a,b}.txt
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
run $cmd -v
[ "$status" -eq 0 ]
# [[ ${lines[0]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[1]} = "2 entries in the database. 2 entries new:" ]]
[[ ${lines[2]} = " ./nonemptydirs/dir2/new-file-a.txt" ]]
[[ ${lines[3]} = " ./nonemptydirs/dir2/new-file-b.txt" ]]
[[ ${lines[4]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects modified files in a tree dir" {
sleep 2
echo $RANDOM >> nonemptydirs/dir2/new-file-a.txt
run $cmd -v
[ "$status" -eq 0 ]
[[ ${lines[0]} = "Checking bitrot.db integrity... ok." ]]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "2 entries in the database. 1 entries updated:" ]]
[[ ${lines[3]} = " ./nonemptydirs/dir2/new-file-a.txt" ]]
[[ ${lines[4]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects renamed files in a tree dir" {
sleep 1
mv nonemptydirs/dir2/new-file-a.txt nonemptydirs/dir2/new-file-a.txt2
run $cmd -v
[ "$status" -eq 0 ]
[[ ${lines[0]} = "Checking bitrot.db integrity... ok." ]]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "2 entries in the database. 1 entries renamed:" ]]
[[ ${lines[3]} = " from ./nonemptydirs/dir2/new-file-a.txt to ./nonemptydirs/dir2/new-file-a.txt2" ]]
[[ ${lines[4]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects delete files in a tree dir" {
sleep 1
rm nonemptydirs/dir2/new-file-a.txt2
run $cmd -v
[ "$status" -eq 0 ]
[[ ${lines[0]} = "Checking bitrot.db integrity... ok." ]]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "1 entries in the database. 1 entries missing:" ]]
[[ ${lines[3]} = " ./nonemptydirs/dir2/new-file-a.txt2" ]]
[[ ${lines[4]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects new files and modified in a tree dir " {
sleep 1
touch more-files-{a,b,c,d,e,f,g}.txt
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
run $cmd -v
[ "$status" -eq 0 ]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "8 entries in the database. 7 entries new:" ]]
[[ ${lines[3]} = " ./more-files-a.txt" ]]
[[ ${lines[4]} = " ./more-files-b.txt" ]]
[[ ${lines[5]} = " ./more-files-c.txt" ]]
[[ ${lines[6]} = " ./more-files-d.txt" ]]
[[ ${lines[7]} = " ./more-files-e.txt" ]]
[[ ${lines[8]} = " ./more-files-f.txt" ]]
[[ ${lines[9]} = " ./more-files-g.txt" ]]
[[ ${lines[10]} = "1 entries updated:" ]]
[[ ${lines[11]} = " ./nonemptydirs/dir2/new-file-b.txt" ]]
[[ ${lines[12]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects new files, modified, deleted and moved in a tree dir " {
sleep 1
for fil in {a,b,c,d,e,f,g}; do
echo $RANDOM >> nonemptydirs/pl-more-files-$fil.txt
done
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
mv more-files-a.txt more-files-a.txt2
rm more-files-g.txt
run $cmd -v
[ "$status" -eq 0 ]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "14 entries in the database. 7 entries new:" ]]
[[ ${lines[3]} = " ./nonemptydirs/pl-more-files-a.txt" ]]
[[ ${lines[4]} = " ./nonemptydirs/pl-more-files-b.txt" ]]
[[ ${lines[5]} = " ./nonemptydirs/pl-more-files-c.txt" ]]
[[ ${lines[6]} = " ./nonemptydirs/pl-more-files-d.txt" ]]
[[ ${lines[7]} = " ./nonemptydirs/pl-more-files-e.txt" ]]
[[ ${lines[8]} = " ./nonemptydirs/pl-more-files-f.txt" ]]
[[ ${lines[9]} = " ./nonemptydirs/pl-more-files-g.txt" ]]
[[ ${lines[10]} = "1 entries updated:" ]]
[[ ${lines[11]} = " ./nonemptydirs/dir2/new-file-b.txt" ]]
[[ ${lines[12]} = "1 entries renamed:" ]]
[[ ${lines[13]} = " from ./more-files-a.txt to ./more-files-a.txt2" ]]
[[ ${lines[14]} = "1 entries missing:" ]]
[[ ${lines[15]} = " ./more-files-g.txt" ]]
[[ ${lines[16]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot detects new files, modified, deleted and moved in a tree dir 2" {
sleep 1
for fil in {a,b,c,d,e,f,g}; do
echo $RANDOM >> nonemptydirs/pl2-more-files-$fil.txt
done
echo $RANDOM >> nonemptydirs/pl-more-files-a.txt
mv nonemptydirs/pl-more-files-b.txt nonemptydirs/pl-more-files-b.txt2
cp nonemptydirs/pl-more-files-g.txt nonemptydirs/pl2-more-files-g.txt2
cp nonemptydirs/pl-more-files-d.txt nonemptydirs/pl2-more-files-d.txt2
rm more-files-f.txt nonemptydirs/pl-more-files-c.txt
run $cmd -v
[ "$status" -eq 0 ]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "21 entries in the database. 9 entries new:" ]]
[[ ${lines[3]} = " ./nonemptydirs/pl2-more-files-a.txt" ]]
[[ ${lines[4]} = " ./nonemptydirs/pl2-more-files-b.txt" ]]
[[ ${lines[5]} = " ./nonemptydirs/pl2-more-files-c.txt" ]]
[[ ${lines[6]} = " ./nonemptydirs/pl2-more-files-d.txt" ]]
[[ ${lines[7]} = " ./nonemptydirs/pl2-more-files-d.txt2" ]]
[[ ${lines[8]} = " ./nonemptydirs/pl2-more-files-e.txt" ]]
[[ ${lines[9]} = " ./nonemptydirs/pl2-more-files-f.txt" ]]
[[ ${lines[10]} = " ./nonemptydirs/pl2-more-files-g.txt" ]]
[[ ${lines[11]} = " ./nonemptydirs/pl2-more-files-g.txt2" ]]
[[ ${lines[12]} = "1 entries updated:" ]]
[[ ${lines[13]} = " ./nonemptydirs/pl-more-files-a.txt" ]]
[[ ${lines[14]} = "1 entries renamed:" ]]
[[ ${lines[15]} = " from ./nonemptydirs/pl-more-files-b.txt to ./nonemptydirs/pl-more-files-b.txt2" ]]
[[ ${lines[16]} = "2 entries missing:" ]]
[[ ${lines[17]} = " ./more-files-f.txt" ]]
[[ ${lines[18]} = " ./nonemptydirs/pl-more-files-c.txt" ]]
[[ ${lines[19]} = "Updating bitrot.sha512... done." ]]
}
@test "bitrot can operate with 3278 files easily in a dir (1)" {
sleep 1
mkdir -p alotfiles/here; cd alotfiles/here
# create a 320KB file
dd if=/dev/urandom of=masterfile bs=1 count=327680
# split it in 3277 files (instantly) + masterfile = 3278
split -b 100 -a 10 masterfile
cd $test_dir
run $cmd
[ "$status" -eq 0 ]
[[ ${lines[2]} = "3299 entries in the database, 3278 new, 0 updated, 0 renamed, 0 missing." ]]
}
@test "bitrot can operate with 3278 files easily in a dir (2)" {
sleep 1
mv alotfiles/here alotfiles/here-moved
run $cmd
[ "$status" -eq 0 ]
[[ ${lines[2]} = "3299 entries in the database, 0 new, 0 updated, 3278 renamed, 0 missing." ]]
}
@test "bitrot can detect rotten bits in a dir (1)" {
sleep 1
touch non-rotten-file
dd if=/dev/zero of=rotten-file bs=1k count=1000 &>/dev/null
# let's make sure they share the same timestamp
touch -r non-rotten-file rotten-file
run $cmd -v
[ "$status" -eq 0 ]
[[ ${lines[0]} = "Checking bitrot.db integrity... ok." ]]
# [[ ${lines[1]} = "Finished. 0.00 MiB of data read. 0 errors found." ]]
[[ ${lines[2]} = "3301 entries in the database, 2 entries new:" ]]
[[ ${lines[3]} = " ./non-rotten-file" ]]
[[ ${lines[4]} = " ./rotten-file" ]]
}
@test "bitrot can detect rotten bits in a dir (2)" {
sleep 1
# modify the rotten file...
dd if=/dev/urandom of=rotten-file bs=1k count=10 seek=1k conv=notrunc &>/dev/null
# ...but revert the modification date
touch -r non-rotten-file rotten-file
run $cmd -q
[ "$status" -eq 1 ]
[[ ${lines[0]} = *"error: SHA1 mismatch for ./rotten-file: expected"* ]]
[[ ${lines[1]} = "error: There were 1 errors found." ]]
}
@test "Clean everything" {
run chmod -Rf a+w $test_dir
run rm -rf $test_dir
}

337
tests/test_bitrot.py Normal file
View File

@ -0,0 +1,337 @@
"""
NOTE: those tests are ordered and require pytest-order to run correctly.
"""
import getpass
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
from textwrap import dedent
import pytest
TMP = Path("/tmp/")
ReturnCode = int
StdOut = list[str]
StdErr = list[str]
def bitrot(*args: str) -> tuple[ReturnCode, StdOut, StdErr]:
cmd = [sys.executable, "-m", "bitrot"]
cmd.extend(args)
res = subprocess.run(shlex.join(cmd), shell=True, capture_output=True)
stdout = (res.stdout or b"").decode("utf8")
stderr = (res.stderr or b"").decode("utf8")
return res.returncode, lines(stdout), lines(stderr)
def bash(script, empty_dir: bool = False) -> bool:
username = getpass.getuser()
test_dir = TMP / f"bitrot-dir-{username}"
if empty_dir and test_dir.is_dir():
os.chdir(TMP)
shutil.rmtree(test_dir)
test_dir.mkdir(exist_ok=True)
os.chdir(test_dir)
preamble = """
set -euxo pipefail
LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
"""
if script:
# We need to wait a second for modification timestamps to differ so that
# the ordering of the output stays the same every run of the tests.
preamble += """
sleep 1
"""
script_path = TMP / "bitrot-test.bash"
script_path.write_text(dedent(preamble + script))
script_path.chmod(0o755)
out = subprocess.run(["bash", str(script_path)], capture_output=True)
if out.returncode:
print(f"Non-zero return code {out.returncode} when running {script_path}")
if out.stdout:
print(out.stdout)
if out.stderr:
print(out.stderr)
return False
return True
def lines(s: str) -> list[str]:
r"""Only return non-empty lines that weren't killed by \r."""
return [
line.rstrip()
for line in s.splitlines(keepends=True)
if line and line.rstrip() and line[-1] != "\r"
]
@pytest.mark.order(1)
def test_command_exists() -> None:
rc, out, err = bitrot("--help")
assert rc == 0
assert not err
assert out[0].startswith("usage:")
assert bash("", empty_dir=True)
@pytest.mark.order(2)
def test_new_files_in_a_tree_dir() -> None:
assert bash(
"""
mkdir -p nonemptydirs/dir2/
touch nonemptydirs/dir2/new-file-{a,b}.txt
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
# assert out[0] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[1] == "2 entries in the database. 2 entries new:"
assert out[2] == " ./nonemptydirs/dir2/new-file-a.txt"
assert out[3] == " ./nonemptydirs/dir2/new-file-b.txt"
assert out[4] == "Updating bitrot.sha512... done."
@pytest.mark.order(3)
def test_modified_files_in_a_tree_dir() -> None:
assert bash(
"""
echo $RANDOM >> nonemptydirs/dir2/new-file-a.txt
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "2 entries in the database. 1 entries updated:"
assert out[3] == " ./nonemptydirs/dir2/new-file-a.txt"
assert out[4] == "Updating bitrot.sha512... done."
@pytest.mark.order(4)
def test_renamed_files_in_a_tree_dir() -> None:
assert bash(
"""
mv nonemptydirs/dir2/new-file-a.txt nonemptydirs/dir2/new-file-a.txt2
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "2 entries in the database. 1 entries renamed:"
o3 = " from ./nonemptydirs/dir2/new-file-a.txt to ./nonemptydirs/dir2/new-file-a.txt2"
assert out[3] == o3
assert out[4] == "Updating bitrot.sha512... done."
@pytest.mark.order(5)
def test_deleted_files_in_a_tree_dir() -> None:
assert bash(
"""
rm nonemptydirs/dir2/new-file-a.txt2
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "1 entries in the database. 1 entries missing:"
assert out[3] == " ./nonemptydirs/dir2/new-file-a.txt2"
assert out[4] == "Updating bitrot.sha512... done."
@pytest.mark.order(5)
def test_new_files_and_modified_files_in_a_tree_dir() -> None:
assert bash(
"""
for fil in {a,b,c,d,e,f,g}; do
echo $fil >> more-files-$fil.txt
done
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "8 entries in the database. 7 entries new:"
assert out[3] == " ./more-files-a.txt"
assert out[4] == " ./more-files-b.txt"
assert out[5] == " ./more-files-c.txt"
assert out[6] == " ./more-files-d.txt"
assert out[7] == " ./more-files-e.txt"
assert out[8] == " ./more-files-f.txt"
assert out[9] == " ./more-files-g.txt"
assert out[10] == "1 entries updated:"
assert out[11] == " ./nonemptydirs/dir2/new-file-b.txt"
assert out[12] == "Updating bitrot.sha512... done."
@pytest.mark.order(6)
def test_new_files_modified_deleted_and_moved_in_a_tree_dir() -> None:
assert bash(
"""
for fil in {a,b,c,d,e,f,g}; do
echo $fil $RANDOM >> nonemptydirs/pl-more-files-$fil.txt
done
echo $RANDOM >> nonemptydirs/dir2/new-file-b.txt
mv more-files-a.txt more-files-a.txt2
rm more-files-g.txt
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "14 entries in the database. 7 entries new:"
assert out[3] == " ./nonemptydirs/pl-more-files-a.txt"
assert out[4] == " ./nonemptydirs/pl-more-files-b.txt"
assert out[5] == " ./nonemptydirs/pl-more-files-c.txt"
assert out[6] == " ./nonemptydirs/pl-more-files-d.txt"
assert out[7] == " ./nonemptydirs/pl-more-files-e.txt"
assert out[8] == " ./nonemptydirs/pl-more-files-f.txt"
assert out[9] == " ./nonemptydirs/pl-more-files-g.txt"
assert out[10] == "1 entries updated:"
assert out[11] == " ./nonemptydirs/dir2/new-file-b.txt"
assert out[12] == "1 entries renamed:"
assert out[13] == " from ./more-files-a.txt to ./more-files-a.txt2"
assert out[14] == "1 entries missing:"
assert out[15] == " ./more-files-g.txt"
assert out[16] == "Updating bitrot.sha512... done."
@pytest.mark.order(7)
def test_new_files_modified_deleted_and_moved_in_a_tree_dir_2() -> None:
assert bash(
"""
for fil in {a,b,c,d,e,f,g}; do
echo $RANDOM >> nonemptydirs/pl2-more-files-$fil.txt
done
echo $RANDOM >> nonemptydirs/pl-more-files-a.txt
mv nonemptydirs/pl-more-files-b.txt nonemptydirs/pl-more-files-b.txt2
cp nonemptydirs/pl-more-files-g.txt nonemptydirs/pl2-more-files-g.txt2
cp nonemptydirs/pl-more-files-d.txt nonemptydirs/pl2-more-files-d.txt2
rm more-files-f.txt nonemptydirs/pl-more-files-c.txt
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "21 entries in the database. 9 entries new:"
assert out[3] == " ./nonemptydirs/pl2-more-files-a.txt"
assert out[4] == " ./nonemptydirs/pl2-more-files-b.txt"
assert out[5] == " ./nonemptydirs/pl2-more-files-c.txt"
assert out[6] == " ./nonemptydirs/pl2-more-files-d.txt"
assert out[7] == " ./nonemptydirs/pl2-more-files-d.txt2"
assert out[8] == " ./nonemptydirs/pl2-more-files-e.txt"
assert out[9] == " ./nonemptydirs/pl2-more-files-f.txt"
assert out[10] == " ./nonemptydirs/pl2-more-files-g.txt"
assert out[11] == " ./nonemptydirs/pl2-more-files-g.txt2"
assert out[12] == "1 entries updated:"
assert out[13] == " ./nonemptydirs/pl-more-files-a.txt"
assert out[14] == "1 entries renamed:"
o15 = " from ./nonemptydirs/pl-more-files-b.txt to ./nonemptydirs/pl-more-files-b.txt2"
assert out[15] == o15
assert out[16] == "2 entries missing:"
assert out[17] == " ./more-files-f.txt"
assert out[18] == " ./nonemptydirs/pl-more-files-c.txt"
assert out[19] == "Updating bitrot.sha512... done."
@pytest.mark.order(8)
def test_3278_files() -> None:
assert bash(
"""
mkdir -p alotfiles/here; cd alotfiles/here
# create a 320KB file
dd if=/dev/urandom of=masterfile bs=1 count=327680
# split it in 3277 files (instantly) + masterfile = 3278
split -b 100 -a 10 masterfile
"""
)
rc, out, err = bitrot()
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
o2 = "3299 entries in the database, 3278 new, 0 updated, 0 renamed, 0 missing."
assert out[2] == o2
@pytest.mark.order(9)
def test_3278_files_2() -> None:
assert bash(
"""
mv alotfiles/here alotfiles/here-moved
"""
)
rc, out, err = bitrot()
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
o2 = "3299 entries in the database, 0 new, 0 updated, 3278 renamed, 0 missing."
assert out[2] == o2
@pytest.mark.order(10)
def test_rotten_file() -> None:
assert bash(
"""
touch non-rotten-file
dd if=/dev/zero of=rotten-file bs=1k count=1000 &>/dev/null
# let's make sure they share the same timestamp
touch -r non-rotten-file rotten-file
"""
)
rc, out, err = bitrot("-v")
assert rc == 0
assert not err
assert out[0] == "Checking bitrot.db integrity... ok."
# assert out[1] == "Finished. 0.00 MiB of data read. 0 errors found."
assert out[2] == "3301 entries in the database. 2 entries new:"
assert out[3] == " ./non-rotten-file"
assert out[4] == " ./rotten-file"
@pytest.mark.order(11)
def test_rotten_file_2() -> None:
assert bash(
"""
# modify the rotten file...
dd if=/dev/urandom of=rotten-file bs=1k count=10 seek=1k conv=notrunc &>/dev/null
# ...but revert the modification date
touch -r non-rotten-file rotten-file
"""
)
rc, out, err = bitrot("-q")
assert rc == 1
assert not out
e = (
"error: SHA1 mismatch for ./rotten-file: expected"
" 8fee1653e234fee8513245d3cb3e3c06d071493e, got"
)
assert err[0].startswith(e)
assert err[1] == "error: There were 1 errors found."

View File

@ -0,0 +1,2 @@
pytest
pytest-order