diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cc5b5d4 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# GNU Make file for the automation of pytest for date2name. +# +# While the test script is written for Python 3.9.2, you might need to +# adjust the following instruction once in case your OS includes pyest +# for legacy Python 2 side by side to Python 3, or only hosts pytest +# for Python 3. The tests in script test_date2name.py are set up to +# work with pytest for Python 3; dependent on your installation, which +# may be named pytest-3, or (again) pytest. +# +# Put this file like test_date2name.py in the root folder of date2name +# fetched from PyPi or GitHub. Then run +# +# chmod +x * +# make ./Makefile +# +# to run the tests. The test sequence either stops at the first test +# failing, or after completion. + +# pytest -xv test_date2name.py # only pytest for Python 3 is present +pytest-3 -xv test_date2name.py # pytest if Python 2 and Python 3 coexist diff --git a/test_date2name.py b/test_date2name.py new file mode 100644 index 0000000..ab21598 --- /dev/null +++ b/test_date2name.py @@ -0,0 +1,192 @@ +#!/bin/usr/env python3 + +# name: test_date2name.py +# author: nbehrnd@yahoo.com +# license: GPL v3, 2021. +# date: 2021-08-30 (YYYY-MM-DD) +# edit: 2021-09-02 (YYYY-MM-DD) +# +"""Test pad for functions by date2name with pytest. + +Written for Python 3.9.2 and pytest 6.2.4 for Python 3 as provided by +Linux Debian 12/bookworm, branch testing, this is a programmatic check +of functions offered by date2name. Deposit this script in the root of +the folder fetched and unzipped from PyPi or GitHub. If your system +includes both legacy Python 2 and Python 3, pytest for Python 3 likely +is named pytest-3; otherwise only pytest. Thus, adjust your input on +the CLI accordingly when running either one of + +pytest -xv test_date2name.py +pytest-3 -xv test_date2name.py + +The script either stops when one of the tests fail, or after completion +of the test sequence. In both cases, the progress of the ongoing tests +is reported to the CLI.""" +import os +import time + +from datetime import datetime +from subprocess import getstatusoutput, getoutput + +import pytest + +PROGRAM = str("./date2name/__init__.py") +TFILE = str("test_file.txt") # the intermediate test file written + +def prepare_testfile(): + """The creation of the test file.""" + with open (TFILE, mode="w") as newfile: + newfile.write("This is the test file for test_date2name.py.") + # adjust modification time stamp, based on + # https://stackoverflow.com/questions/53111614/how-to-modify-the-file-modification-date-with-python-on-mac + result = os.stat(TFILE) + os.utime(TFILE, (result.st_atime, result.st_mtime + 10.0)) + + +def query_file_creation(): + """Determine the time of creation of the file.""" + created = os.stat(TFILE).st_ctime + created = str(datetime.fromtimestamp(created)) + return created + + +def query_file_modification(): + """Determine the time when the file was modified.""" + modified = os.stat(TFILE).st_mtime + modified = str(datetime.fromtimestamp(modified)) + return modified + +def test_create_remove_testfile(): + """Merely check if the test file may be written and removed.""" + prepare_testfile() + os.remove(TFILE) + + +def test_script_existance(): + """Merely check for the script's presence.""" + assert os.path.isfile(PROGRAM) + + +def test_script_version(): + """Check for the correct output of the version. + + CLI equivalence: date2name --version """ + out = getoutput(f"python3 {PROGRAM} --version") + assert out.strip() == "__init__.py 2018-05-09" + +@pytest.mark.parametrize("arg1", [" ", "-f", "--files", + "-m", "--mtime", + "-c", "--ctime"]) +def test_default_pattern_YYYY_MM_DD(arg1): + """Prepend YYYY-MM-DD to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in [" ", "-f", "--files", "-m", "--mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-c", "--ctime"]: + day = query_file_creation().split()[0] + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + +@pytest.mark.parametrize("arg1", ["-C", "--compact", + "-C -f", "--compact -f", + "-C --files", "--compact --files", + "-C -m", "--compact -m", + "-C --mtime", "--compact --mtime", + "-C -c", "--compact -c", + "-C --ctime", "--compact --ctime"]) +def test_compact_pattern_YYYYMMDD(arg1): + """Prepend YYYYMMDD to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-C", "--compact", + "-C -f", "--compact -f", + "-C --files", "--compact --files", + "-C -m", "--compact -m", + "-C --mtime", "--compact --mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-C -c", "--compact -c", + "-C --ctime", "--compact --ctime"]: + day = query_file_creation().split()[0] + + # drop the hyphens in the date stamp: + day = day.replace("-", "") + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + +@pytest.mark.parametrize("arg1", ["-M", "--month", + "-M -f", "--month -f", + "-M --files", "--month --files", + "-M -m", "--month -m", + "-M --mtime", "--month --mtime", + "-M -c", "--month -c", + "-M --ctime", "--month --ctime"]) +def test_compact_monthn_YYYY_MM(arg1): + """Prepend YYYY-MM to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-M", "--month", + "-M -f", "--month -f", + "-M --files", "--month --files", + "-M -m", "--month -m", + "-M --mtime", "--month --mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-M -c", "--month -c", + "-M --ctime", "--month --ctime"]: + day = query_file_creation().split()[0] + + # trim off the last three characters in the date stamp: + day = day[:-3] + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + +@pytest.mark.parametrize("arg1", ["-w -f", "-w --files", + "--withtime -f", "--withtime --files", + "-w -m", "-w --mtime", + "--withtime -m", "--withtime --mtime", + "-w -c", "-w --ctime", + "--withtime -c", "--withtime --ctime"]) +def test_default_pattern_YYYY_MM_DDThh_mm_ss(arg1): + """Prepend YYYY-MM-DDThh.mm.ss to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-w -f", "-w --files", + "--withtime -f", "--withtime --files", + "-w -m", "-w --mtime", + "--withtime -m", "--withtime --mtime"]: + day = query_file_modification().split()[0] + second = query_file_modification().split()[1] + + elif arg1 in ["-w -c", "-w --ctime", + "--withtime -c", "--withtime --ctime"]: + day = query_file_creation().split()[0] + second = query_file_creation().split()[1] + + second = second.split(".")[0] # use integer seconds only + second = second.replace(":", ".") # adjust represetation + + new = "".join([day, "T", second, "_", TFILE]) + + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) diff --git a/test_generator.org b/test_generator.org new file mode 100755 index 0000000..9acad9b --- /dev/null +++ b/test_generator.org @@ -0,0 +1,316 @@ +#+NAME: test_generator.org +#+AUTHOR: nbehrnd@yahoo.com +#+DATE: 2021-09-02 (YYYY-MM-DD) +# License: GPL3, 2021. + +#+PROPERTY: header-args :tangle yes +# Export the tangled files with C-c C-v t + +* Intent + + The application =date2name= by Karl Voit /et al./ ([[https://github.com/novoid/date2name][source)]] prepepends date + stamps to files and folders (YYYY-MM-DD, YYYYMMDD, YYYY-MM, and + YYYY-MM-DDThh.mm.ss). This Emacs .org file is used to prepare the automatic + testing of the file processing by [[https://docs.pytest.org/en/latest/][pytest]]. + + By the command =C-c C-v t=, this =.org= file may (re)generate the tangled test + script, file =test_date2name.py= as well as a dedicated =Makefile= to ease the + automated testing even further. =pytest= is not part of the Python standard + library, but may be obtained easily e.g., from [[https://pypi.org/project/pytest/][PyPi]]. + + It is advantageous to retain the options to run =pytest= in a =Makefile=, + which equally is inlcuded in this development e.g. to retain if an + instruction like =pytest -xv= or =pytest-3 -xv= is necessary (/vide infra/). + +* Dependencies + + The testing script is set up with Python 3.9.2 in mind. Thus, to run the + tests successfully, you need a working installation of Python 3 with pytest + for Python 3. + +* Deployment + + On a computer with Python 3 only, the recommended call on the CLI to run the + tests is either one of the following instructions (you might need to add the + executable bit): + + python pytest -xv test_date2name.py + ./Makefile + + In case the computer you use equally includes an installation of legacy + Python 2 side-by-side to Python 3, you must explicitly call for the later + branch of the two. Depending on your OS, this requires an adjustment of the + command issue. In Linux Debian 12/bookworm, branch testing, for example, + + python3 pytest-3 -xv test_date2name.py + + or, after adjustment of the =Makefile= and provision of the executable bit + + ./Makefile + + +* Building the tests + +** Building of the Makefile + + Set up for GNU Make 4.3, as provided from the repositories of Linux Debian 12 + (bookworm), branch testing. + + #+BEGIN_SRC makefile :tangle Makefile + # GNU Make file for the automation of pytest for date2name. + # + # While the test script is written for Python 3.9.2, you might need to + # adjust the following instruction once in case your OS includes pyest + # for legacy Python 2 side by side to Python 3, or only hosts pytest + # for Python 3. The tests in script test_date2name.py are set up to + # work with pytest for Python 3; dependent on your installation, which + # may be named pytest-3, or (again) pytest. + # + # Put this file like test_date2name.py in the root folder of date2name + # fetched from PyPi or GitHub. Then run + # + # chmod +x * + # make ./Makefile + # + # to run the tests. The test sequence either stops at the first test + # failing, or after completion. + + # pytest -xv test_date2name.py # only pytest for Python 3 is present + pytest-3 -xv test_date2name.py # pytest if Python 2 and Python 3 coexist + #+end_src + + +** Building the test script + +*** header section + #+BEGIN_SRC python :tangle test_date2name.py + #!/bin/usr/env python3 + + # name: test_date2name.py + # author: nbehrnd@yahoo.com + # license: GPL v3, 2021. + # date: 2021-08-30 (YYYY-MM-DD) + # edit: 2021-09-02 (YYYY-MM-DD) + # + """Test pad for functions by date2name with pytest. + + Written for Python 3.9.2 and pytest 6.2.4 for Python 3 as provided by + Linux Debian 12/bookworm, branch testing, this is a programmatic check + of functions offered by date2name. Deposit this script in the root of + the folder fetched and unzipped from PyPi or GitHub. If your system + includes both legacy Python 2 and Python 3, pytest for Python 3 likely + is named pytest-3; otherwise only pytest. Thus, adjust your input on + the CLI accordingly when running either one of + + pytest -xv test_date2name.py + pytest-3 -xv test_date2name.py + + The script either stops when one of the tests fail, or after completion + of the test sequence. In both cases, the progress of the ongoing tests + is reported to the CLI.""" + import os + import time + + from datetime import datetime + from subprocess import getstatusoutput, getoutput + + import pytest + + PROGRAM = str("./date2name/__init__.py") + TFILE = str("test_file.txt") # the intermediate test file written + #+end_src + + +*** prepare recurrently used functions + + Define actions which are going to be used multiple times. + + #+begin_src python :tangle test_date2name.py + def prepare_testfile(): + """The creation of the test file.""" + with open (TFILE, mode="w") as newfile: + newfile.write("This is the test file for test_date2name.py.") + # adjust modification time stamp, based on + # https://stackoverflow.com/questions/53111614/how-to-modify-the-file-modification-date-with-python-on-mac + result = os.stat(TFILE) + os.utime(TFILE, (result.st_atime, result.st_mtime + 10.0)) + + + def query_file_creation(): + """Determine the time of creation of the file.""" + created = os.stat(TFILE).st_ctime + created = str(datetime.fromtimestamp(created)) + return created + + + def query_file_modification(): + """Determine the time when the file was modified.""" + modified = os.stat(TFILE).st_mtime + modified = str(datetime.fromtimestamp(modified)) + return modified + #+end_src + + +*** set up very elementary tests + + These tests do not modify a file, nor folder by =date2time=. + + #+begin_src python :tangle test_date2name.py + def test_create_remove_testfile(): + """Merely check if the test file may be written and removed.""" + prepare_testfile() + os.remove(TFILE) + + + def test_script_existance(): + """Merely check for the script's presence.""" + assert os.path.isfile(PROGRAM) + + + def test_script_version(): + """Check for the correct output of the version. + + CLI equivalence: date2name --version """ + out = getoutput(f"python3 {PROGRAM} --version") + assert out.strip() == "__init__.py 2018-05-09" + #+end_src + + +*** perform the tests on files [4/4] + + These are tests for prepending the time stamp to a file name without + explicit call to query for the time of modification. + + [X] default pattern, i.e. prepend YYYY-MM-DD_ to file test.txt + #+begin_src python :tangle test_date2name.py + @pytest.mark.parametrize("arg1", [" ", "-f", "--files", + "-m", "--mtime", + "-c", "--ctime"]) + def test_default_pattern_YYYY_MM_DD(arg1): + """Prepend YYYY-MM-DD to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in [" ", "-f", "--files", "-m", "--mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-c", "--ctime"]: + day = query_file_creation().split()[0] + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + #+end_src + + + [X] prepend the day in the compact format YYYYMMDD_ to file test.txt + This may re-use much of the instructions used for the default pattern + and only needs to drop the hyphens. + #+begin_src python :tangle test_date2name.py + @pytest.mark.parametrize("arg1", ["-C", "--compact", + "-C -f", "--compact -f", + "-C --files", "--compact --files", + "-C -m", "--compact -m", + "-C --mtime", "--compact --mtime", + "-C -c", "--compact -c", + "-C --ctime", "--compact --ctime"]) + def test_compact_pattern_YYYYMMDD(arg1): + """Prepend YYYYMMDD to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-C", "--compact", + "-C -f", "--compact -f", + "-C --files", "--compact --files", + "-C -m", "--compact -m", + "-C --mtime", "--compact --mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-C -c", "--compact -c", + "-C --ctime", "--compact --ctime"]: + day = query_file_creation().split()[0] + + # drop the hyphens in the date stamp: + day = day.replace("-", "") + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + #+end_src + + + [X] Prepend year and month in the format YYYY-MM_ to file test.txt. + Departing from the standard format YYYY-MM-DD, it suffices to trim + off the last three characters. + #+begin_src python :tangle test_date2name.py + @pytest.mark.parametrize("arg1", ["-M", "--month", + "-M -f", "--month -f", + "-M --files", "--month --files", + "-M -m", "--month -m", + "-M --mtime", "--month --mtime", + "-M -c", "--month -c", + "-M --ctime", "--month --ctime"]) + def test_compact_monthn_YYYY_MM(arg1): + """Prepend YYYY-MM to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-M", "--month", + "-M -f", "--month -f", + "-M --files", "--month --files", + "-M -m", "--month -m", + "-M --mtime", "--month --mtime"]: + day = query_file_modification().split()[0] + + elif arg1 in ["-M -c", "--month -c", + "-M --ctime", "--month --ctime"]: + day = query_file_creation().split()[0] + + # trim off the last three characters in the date stamp: + day = day[:-3] + + new = "_".join([day, TFILE]) + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + #+end_src + + + [X] To prepend date and time to file test.txt in a pattern of + YYYY-MM-DDThh.mm.ss, the default pattern YYYY-MM-DD is extended. + #+begin_src python :tangle test_date2name.py + @pytest.mark.parametrize("arg1", ["-w -f", "-w --files", + "--withtime -f", "--withtime --files", + "-w -m", "-w --mtime", + "--withtime -m", "--withtime --mtime", + "-w -c", "-w --ctime", + "--withtime -c", "--withtime --ctime"]) + def test_default_pattern_YYYY_MM_DDThh_mm_ss(arg1): + """Prepend YYYY-MM-DDThh.mm.ss to the file.""" + prepare_testfile() + day = str("") + new = str("") + + if arg1 in ["-w -f", "-w --files", + "--withtime -f", "--withtime --files", + "-w -m", "-w --mtime", + "--withtime -m", "--withtime --mtime"]: + day = query_file_modification().split()[0] + second = query_file_modification().split()[1] + + elif arg1 in ["-w -c", "-w --ctime", + "--withtime -c", "--withtime --ctime"]: + day = query_file_creation().split()[0] + second = query_file_creation().split()[1] + + second = second.split(".")[0] # use integer seconds only + second = second.replace(":", ".") # adjust represetation + + new = "".join([day, "T", second, "_", TFILE]) + + test = getoutput(f"python3 {PROGRAM} {TFILE} {arg1}") + assert os.path.isfile(new) + os.remove(new) + #+end_src +