fix: rebuild pytest sections 2 and 3 for Linux and Windows

The second and third section of the pytest tests were rewritten
to be more portable for either Linux Debian 13, or Windows 10.

As confirmed during this revision, the outcome of the optional
--smart-prepend mode still depends on the format of the leading
time stamp.

Second, special characters presumed to provide a file name without
a white space (for instance the underscore), though working well in
Linux Debian, are not reliably processed in Windows.  To facilitate
a subsequent refactoring of appendfilename, the corresponding tests
already are present, however currently muted.

Thirdly, the reinstallment of pytest.ini and labels allows again
to address the pytest tests by the name of their set: default,
prepend, and smart.

Signed-off-by: Norwid Behrnd <nbehrnd@yahoo.com>
This commit is contained in:
Norwid Behrnd 2024-11-06 00:06:21 +01:00
commit 297ee62bbc
5 changed files with 625 additions and 281 deletions

View file

@ -19,7 +19,7 @@ on:
jobs: jobs:
test-ubuntu-2404: test-ubuntu-2404:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
timeout-minutes: 2 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -34,11 +34,14 @@ jobs:
run: pip install -r requirements.txt run: pip install -r requirements.txt
- name: run the check by pytest - name: run the check by pytest
run: python -m pytest run: |
python -m pytest -m "default"
python -m pytest -m "prepend"
python -m pytest -m "smart"
test-windows-2022: test-windows-2022:
runs-on: windows-2022 runs-on: windows-2022
timeout-minutes: 2 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -53,11 +56,15 @@ jobs:
run: pip install -r requirements.txt run: pip install -r requirements.txt
- name: run the check by pytest - name: run the check by pytest
run: python -m pytest run: |
python -m pytest -m "default"
python -m pytest -m "prepend"
python -m pytest -m "smart"
test-macos-14: test-macos-14:
runs-on: macos-14 runs-on: macos-14
timeout-minutes: 2 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -72,4 +79,8 @@ jobs:
run: pip install -r requirements.txt run: pip install -r requirements.txt
- name: run the check by pytest - name: run the check by pytest
run: python -m pytest run: |
python -m pytest -m "default"
python -m pytest -m "prepend"
python -m pytest -m "smart"

View file

@ -26,7 +26,7 @@ FILENAME_TAG_SEPARATOR = ' -- ' # between file name and (optional) list of tags
BETWEEN_TAG_SEPARATOR = ' ' # between tags (not that relevant in this tool) BETWEEN_TAG_SEPARATOR = ' ' # between tags (not that relevant in this tool)
DEFAULT_TEXT_SEPARATOR = ' ' # between old file name and inserted text DEFAULT_TEXT_SEPARATOR = ' ' # between old file name and inserted text
RENAME_SYMLINK_ORIGINALS_WHEN_RENAMING_SYMLINKS = True # if current file is a symlink with the same name, also rename source file RENAME_SYMLINK_ORIGINALS_WHEN_RENAMING_SYMLINKS = True # if current file is a symlink with the same name, also rename source file
WITHTIME_AND_SECONDS_PATTERN = re.compile('^(\d{4,4}-[01]\d-[0123]\d(([T :_-])([012]\d)([:.-])([012345]\d)(([:.-])([012345]\d))?)?)[- _.](.+)') WITHTIME_AND_SECONDS_PATTERN = re.compile(r"^(\d{4,4}-[01]\d-[0123]\d(([T :_-])([012]\d)([:.-])([012345]\d)(([:.-])([012345]\d))?)?)[- _.](.+)")
USAGE = "\n\ USAGE = "\n\
appendfilename [<options>] <list of files>\n\ appendfilename [<options>] <list of files>\n\
@ -60,13 +60,13 @@ Example usages:\n\
# file names containing optional tags matches following regular expression # file names containing optional tags matches following regular expression
FILE_WITH_EXTENSION_REGEX = re.compile("(.*?)(( -- .*)?(\.\w+?)?)$") FILE_WITH_EXTENSION_REGEX = re.compile(r"(.*?)(( -- .*)?(\.\w+?)?)$")
FILE_WITH_EXTENSION_BASENAME_INDEX = 1 FILE_WITH_EXTENSION_BASENAME_INDEX = 1
FILE_WITH_EXTENSION_TAGS_AND_EXT_INDEX = 2 FILE_WITH_EXTENSION_TAGS_AND_EXT_INDEX = 2
# RegEx which defines "what is a file name component" for tab completion: # RegEx which defines "what is a file name component" for tab completion:
FILENAME_COMPONENT_REGEX = re.compile("[a-zA-Z]+") FILENAME_COMPONENT_REGEX = re.compile(r"[a-zA-Z]+")
# blacklist of lowercase strings that are being ignored for tab completion # blacklist of lowercase strings that are being ignored for tab completion
FILENAME_COMPONENT_LOWERCASE_BLACKLIST = ['img', 'eine', 'einem', 'eines', 'fuer', 'haben', FILENAME_COMPONENT_LOWERCASE_BLACKLIST = ['img', 'eine', 'einem', 'eines', 'fuer', 'haben',

5
pytest.ini Normal file
View file

@ -0,0 +1,5 @@
[pytest]
Markers =
default: appendfilename's default string insertions
prepend: appendfilename's optional -p/--prepend flag
smart: appendfilename's optional --smart-prepend flag

315
test_appendfilename.py Normal file → Executable file
View file

@ -4,66 +4,299 @@
# author: nbehrnd@yahoo.com # author: nbehrnd@yahoo.com
# license: GPL v3, 2022. # license: GPL v3, 2022.
# date: 2022-01-05 (YYYY-MM-DD) # date: 2022-01-05 (YYYY-MM-DD)
# edit: [2024-10-31 Thu] # edit: [2024-11-05 Tue]
#
"""Test pad for functions by appendfilename with pytest. """Test pad for functions by appendfilename with pytest.
Written for Python 3.9.9 and pytest 6.2.4 for Python 3 as provided by Initially written for Python 3.9.9 and pytest 6.2.4 and recently update
Linux Debian 12/bookworm, branch testing, this is a programmatic check for Python 3.12.6/pytest 8.3.3, this script provides a programmatic check
of functions offered by appendfilename. Deposit this script in the root of of functions offered by appendfilename. Deposit this script in the root of
the folder fetched and unzipped from PyPi or GitHub. If your system the folder fetched and unzipped from PyPi or GitHub. Create a virtual
includes both legacy Python 2 and Python 3, pytest for Python 3 likely environment for Python, e.g. by
is named pytest-3; otherwise only pytest. Thus, adjust your input on
the CLI accordingly when running either one of
pytest -v test_appendfilename.py ```shell
pytest-3 -v test_appendfilename.py python -m venv sup
```
These instruction initiate a verbose testing (flag -v) reported back to the In the activated virtual environment, ensure the dependencies are met -
CLI.re will be a verbose report to the CLI The script either stops when one of either by `pip install pyreadline3 pytest`, or `pip install -r requirements.txt`
the tests fail (flag -x), or after completion of the test sequence. In both - and launch the tests by
cases, the progress of the ongoing tests is reported to the CLI (flag -v)."""
```shell
python -m pytest
```
As a reminder, the following optional pytest flags may be useful to obtain
a report tailored to your needs:
- `-x` exits right after the first failing test (reported by `E` instead of `.`)
- `-v` provide a more verbose output
- `-s` equally report the test criterion, e.g. the queried file name
"""
import re import re
import os import os
import pytest import shlex
import sys import sys
import subprocess import subprocess
from pathlib import Path from itertools import product
from subprocess import getstatusoutput, getoutput
PROGRAM = str(Path("appendfilename") / "__init__.py") # Cross-platform path import pytest
PROGRAM = os.path.join("appendfilename", "__init__.py") # Cross-platform path
# The following section tests the applications default pattern where a string
# is added to the file name, just prior to the file's file extension. The
# permutation of the three arguments and their levels defines 120 tests.
arg1_values = [
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
]
arg2_values = [
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
"--separator '!'",
"--separator '@'",
"--separator '#'",
"--separator '$'",
"--separator '%'",
"--separator '_'",
"--separator '+'",
"--separator '='",
"--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values))
@pytest.mark.default @pytest.mark.default
@pytest.mark.parametrize("arg1", ["test.txt", "2021-12-31_test.txt", @pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
"2021-12-31T18.48.22_test.txt"]) def test_append(arg1, arg2, arg3):
@pytest.mark.parametrize("arg2", ["-t book", "-t book_shelf"])#, """Test default which appends a string just prior file extension
# "--text book", "--text book_shelf"])
#@pytest.mark.parametrize("arg3", [" ", "!", "@", "#", "$", "%", "*", "_", "+",
@pytest.mark.parametrize("arg3", [" ", "!", "@", "#", "$", "%", "_", "+",
"=", "-"])
def test_pattern_s1(arg1, arg2, arg3):
"""Check addition just ahead the file extension.
arg1 the test files to process arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added arg2 the text string to be added
arg3 the explicitly defined text separator (except [a-zA-Z])""" arg3 the separator (at least in Windows 10, do not use `*`)"""
# extract the newly added text information: # create a test file:
text_elements = arg2.split(" ")[1:] with open(arg1, mode="w", encoding="utf-8") as newfile:
text = str(" ".join(text_elements)) newfile.write("This is a place holder.\n")
with open(arg1, mode="w") as newfile: # run the test to be tested:
newfile.write("This is a test file for test_appendfilename.") full_command = ["python", PROGRAM, arg1
] + shlex.split(arg2) + shlex.split(arg3)
subprocess.run(full_command, text = True, check = True)
# Run the command with cross-platform Python executable and file paths # construct the new file name to be tested:
result = subprocess.run( if len(shlex.split(arg3)) == 0:
[sys.executable, PROGRAM, arg1, arg2, f"--separator={arg3}"], separator = " "
capture_output=True, text=True, check=True) else:
separator = shlex.split(arg3)[1]
new_filename = "".join([arg1[:-4], arg3, " ", text, str(".txt")]) new_filename = "".join(
[ arg1[:-4], separator,
shlex.split(arg2)[1], ".txt" ])
print(f"test criterion: {new_filename}") # visible by optional `pytest -s`
# is the new file present?
assert os.path.isfile(new_filename) assert os.path.isfile(new_filename)
# space cleaning # check if the OS can process the new file / space cleaning
os.remove(new_filename) os.remove(new_filename)
assert os.path.isfile(new_filename) is False
# The following section is about tests to prepend a user defined string and
# an adjustable separator to the original file name of the file submitted. By
# permutation of the parameter's levels, this defines 240 tests.
arg1_values = [
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
]
arg2_values = [
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
"--separator '!'",
"--separator '@'",
"--separator '#'",
"--separator '$'",
"--separator '%'",
"--separator '_'",
"--separator '+'",
"--separator '='",
"--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
arg4_values = [
"-p", "--prepend"
]
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values, arg4_values))
@pytest.mark.prepend
@pytest.mark.parametrize("arg1, arg2, arg3, arg4", test_cases)
def test_prepend(arg1, arg2, arg3, arg4):
"""test to prepend a string to the original file name
arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added
arg3 the separator (at least in Windows 10, do not use `*`)
arg4 either short of long form to introduce the string as leading """
# create a test file:
with open(arg1, mode="w", encoding="utf-8") as newfile:
newfile.write("This is a place holder.\n")
# run the test to be tested:
full_command = [
"python", PROGRAM, arg1
] + shlex.split(arg2) + shlex.split(arg3) + shlex.split(arg4)
subprocess.run(full_command, text = True, check = True)
# construct the new file name to be tested:
if len(shlex.split(arg3)) == 0:
separator = " "
else:
separator = shlex.split(arg3)[1]
new_filename = "".join( [ shlex.split(arg2)[1], separator, arg1 ] )
print(f"test criterion: {new_filename}") # visible by optional `pytest -s`
# is the new file present?
assert os.path.isfile(new_filename)
# check if the OS can process the new file / space cleaning
os.remove(new_filename)
assert os.path.isfile(new_filename) is False
# This section tests the insertion of a string into the file's file name
# just after the file's time or date stamp as provided `date2name`.
arg1_values = [
"2021-12-31T18.48.22_test.txt",
"2021-12-31_test.txt",
# "20211231_test.txt", # by now `20211231_test.txt` -> 20211231_test ping.txt
# "2021-12_test.txt", # by now `2021-12_test.txt` -> `2021-12_test ping.txt`
# "211231_test.txt" # by now `211231_test.txt` -> `211231_test ping.txt`
]
arg2_values = [
"-t book",
"-t book_shelf",
"--text book",
"--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
# "--separator '!'",
# "--separator '@'",
# "--separator '#'",
# "--separator '$'",
# "--separator '%'",
# "--separator '_'",
# "--separator '+'",
# "--separator '='",
# "--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
# Contrasting to Linux Debian 13, a `pytest` in Windows 10 revealed every
# of these special characters can not safely used as an additional separator.
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values))
@pytest.mark.smart
@pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
def test_smart_prepend(arg1, arg2, arg3):
"""test the insertion of a new string just past the time stamp
arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added
arg3 the separator (at least in Windows 10, do not use `*`
"""
time_stamp = ""
time_stamp_separator = ""
old_filename_no_timestamp = ""
# create a test file:
with open(arg1, mode="w", encoding="utf-8") as newfile:
newfile.write("this is a placeholder\n")
#run `appendfilename` on this test file
run_appendfilename = " ".join(
["python", PROGRAM, arg1, arg2, arg3, " --smart-prepend"])
subprocess.run(run_appendfilename, shell=True, check = True)
# construct the new file name to be testedt:
old_filename = arg1
# account for the implicit separator, i.e. the single space:
if len(shlex.split(arg3)) == 0:
separator = " "
else:
separator = shlex.split(arg3)[1]
# Time stamps `date2name` provides can be either one of five formats
#
# YYYY-MM-DDTHH.MM.SS `--withtime`
# YYYY-MM-DD default
# YYYYMMDD `--compact`
# YYYY-MM `--month`
# YYMMDD `--short`
# Currently, one observes two patterns by `appendfilename`: one which
# substitutes the separator by `date2name`, the other which retains it.
# Note patterns `compact`, `month`, and `short`, currently append the
# additional string rather than smartly prepend after the date stamp --
# for now, these three are not tested. Equally see discussions 15 and 16,
# https://github.com/novoid/appendfilename/issues/15
# https://github.com/novoid/appendfilename/issues/16
# pattern `--with-time`
if re.search(r"^\d{4}-[012]\d-[0-3]\dT[012]\d\.[0-5]\d\.[0-5]\d", old_filename):
time_stamp = old_filename[:19]
time_stamp_separator = old_filename[19]
old_filename_no_timestamp = old_filename[20:]
# default pattern
elif re.search(r"^\d{4}-[012]\d-[0-3]\d", old_filename):
time_stamp = old_filename[:10]
time_stamp_separator = old_filename[10]
old_filename_no_timestamp = old_filename[11:]
# pattern `--compact` # currently fails
elif re.search(r"^\d{4}[012]\d[0-3]\d", old_filename):
time_stamp = old_filename[:8]
time_stamp_separator = old_filename[8]
old_filename_no_timestamp = old_filename[9:]
# pattern `--month` # currently fails
elif re.search(r"^\d{4}-[012]\d", old_filename):
time_stamp = old_filename[:7]
time_stamp_separator = old_filename[7]
old_filename_no_timestamp = old_filename[8:]
# pattern `--short` # currently fails
elif re.search(r"^\d{4}[012]\d[012]\d", old_filename):
time_stamp = old_filename[:6]
time_stamp_separator = old_filename[6]
old_filename_no_timestamp = old_filename[7:]
new_filename = "".join([time_stamp, #time_stamp_separator,
separator, shlex.split(arg2)[1], separator,
old_filename_no_timestamp ])
# is the new file present?
print("\nnew_filename") # optional check for `pytest -s`
print(new_filename)
assert os.path.isfile(new_filename)
# check if the IS can process the new file / space cleaning
os.remove(new_filename)
assert os.path.isfile(new_filename) is False

View file

@ -1,7 +1,7 @@
# name: test_generator.org # name: test_generator.org
# author: nbehrnd@yahoo.com # author: nbehrnd@yahoo.com
# date: 2022-01-05 (YYYY-MM-DD) # date: 2022-01-05 (YYYY-MM-DD)
# edit: [2024-10-31 Thu] # edit: [2024-11-05 Tue]
# license: GPL3, 2022. # license: GPL3, 2022.
# Export the tangled files with C-c C-v t # Export the tangled files with C-c C-v t
@ -12,34 +12,44 @@
The application =appendfilename= by Karl Voit /et al./ ([[https://github.com/novoid/appendfilename][source]]) The application =appendfilename= by Karl Voit /et al./ ([[https://github.com/novoid/appendfilename][source]])
allows the programmatic addition of user defined strings to one or allows the programmatic addition of user defined strings to one or
multiple already existing file names (e.g., add =travel= to file multiple already existing file names (e.g., add =travel= to file
=example.jpg= to yield =example_travel.jpg=). By the command =C-c =example.jpg= to yield =example_travel.jpg=). This file
C-v t=, Emacs may use the present =.org= file to (re)generate a =test_generator.org= provides the files for a programmatic test
tangled test script, file =test_appendfilename.py= for a of =appendfilename= by means of /code tangle/ from Emacs orgmode.
programmatic testing by [[https://docs.pytest.org/en/latest/][pytest]]. (Though =pytest= is not part of the
Python standard library, it may be obtained easily e.g., from [[https://pypi.org/project/pytest/][PyPi]].)
Optionally, the testing may be run by the equally tangled
=Makefile=.
* Deployment * Deployment
The programmatic tests are set up for pytest for Python 3. It The tests presume a working installation of Python 3. After the
however depends on your installation (and in case of Linux, the activation of a virtual environment, one suitable approach is to
authors of your Linux distribution ([[https://github.com/pytest-dev/pytest/discussions/9481][reference]])) if this utility may resolve the dependencies by
be started by =pytest= (e.g., the pattern in pytest's manual), or by
=pytest-3= by either one of the pattern below:
#+begin_src bash :tangle no #+begin_src shell :tangle no
pytest -v test_appendfilename.py python -m pip install -r requirements.txt
pytest-3 -v test_appendfilename.py
#+end_src #+end_src
As of writing, the later pattern is the to be used e.g., in Linux with data by PyPI. Subsequently, the tests can be launched by
Debian 12/bookworm (branch testing) to discern pytest (for
contemporary Python 3) from pytest (for legacy Python 2).
The =Makefile= this =org= file provides for convenience running #+begin_src shell :tangle no
these tests assumes the later syntax pattern. (It might be python -m pytest
necessary to provide the executable bit to activate the Makefile.) #+end_src
where each period (=.=) indicates a passing, and each =E= a failing
test. An optional flag
- =-v= allows a more verbose output to the CLI
- =-x= stops the sequence after the first failing test
- =-s= occasionally provides information e.g., about the tests' criteria
The tests are organized in sets =default=, =prepend=, and =smart=.
This allows to selectively run only checks which are about the
results by =appendfilename= in the /default/ mode, /prepend/ mode,
or /smart prepend/ mode alone, e.g.
#+begin_src shell :tangle no
python -m pytest -m "prepend"
#+end_src
The tests were revised to work equally well in Linux Debian 13/trixie
(e.g., Python 3.12.6 and pytest 8.3.3) as well as Windows 10.
* Setup of Emacs * Setup of Emacs
@ -99,75 +109,47 @@ pytest-3 -v test_appendfilename.py
* Building the tests * Building the tests
** Building of the Makefile ** Building file =pytest.ini=
The setup is for GNU Make 4.3 as provided e.g., by Linux Debian 12 Markers file =pytest.ini= defines and which are used in file
(bookworm), branch testing. Note, the Makefile tangled is a mere =test_appendfilename.py= allows to constrain the run of
convenient moderator for =test_appendfilename.py=; the eventual =pytest=. Rather than to launch a check on all tests, a call
testing of appendfilename's action does not depend on this like e.g.,
Makefile.
#+BEGIN_SRC makefile :tangle Makefile
# GNU Make file for the automation of pytest for appendfilename
#
# While the test script is written for Python 3.9.2, it depends on
# your installation of pytest (and in case of Linux, the authors of
# your distribution) if pytest for Python 3 is invoked either by
# pytest, or pytest-3. In some distributions, pytest actually may
# invoke pyest for legacy Python 2; the tests in test_date2name.py
# however are incompatible to this.
#
# Put this file like test_appendfilename.py in the root folder of
# appendfilename fetched from PyPi or GitHub. Then run
#
# chmod +x *
# make ./Makefile
#
# to run the tests. If you want pytest to exit the test sequence
# right after the first test failing, use the -x flag to the
# instructions on the CLI in addition to the verbosity flag to (-v).
# pytest -v test_appendfilename.py # the pattern by pytest's manual
pytest-3 -v test_appendfilename.py # the alternative pattern (e.g., Debian 12)
#+end_src
** Building a pytest.ini
This file defines markers to assign tests into groups. This allows to run
=pytest= on a subset rather than all tests (which is set up as the default).
E.g., in presence of =pytest.ini=, a call like
#+begin_src bash :tangle no #+begin_src bash :tangle no
pytest-3 test_appendfilename.py -v -m "default" python -m pytest -m "prepend"
#+end_src #+end_src
constrains the tester's action to all tests labeled as "default" as about the only checks those of set =prepend=. At present, tests are
default position where the text string is added. At present, tests are
grouped as grouped as
+ default; appendfilename's default string insertions
+ prepend; corresponding to appendfilename's optional -p/--prepend flag, and + =default= appendfilename's default string insertions
+ smart; corresponding to appendfilename's optional --smart-prepend flag + =prepend= corresponding to either appendfilename's optional =-p=
or =--prepend= flag, and
It is possible to run one, two, or all three groups in one run of pytest. + =smart= corresponding to appendfilename's optional
E.g., a simultaneous check of tests belonging to either default, or prepend =--smart-prepend= flag
optional requires the instruction
It is possible to run one, two, or all three groups in one run of
pytest, for instance
#+begin_src bash :tangle no #+begin_src bash :tangle no
pytest-3 test_appendfilename.py -m "default and prepend" -v pytest-3 test_appendfilename.py -m "default and prepend" -v
#+end_src #+end_src
#+begin_src python :tangle no The content of =pytest.ini=:
[pytest]
markers =
default: check the default insertion position of appendfile
prepend: check the prepend insertion position of appendfile
smart: check the smart-prepend insertion position of appendfile
#+end_src
#+begin_src shell :tangle pytest.ini
[pytest]
Markers =
default: appendfilename's default string insertions
prepend: appendfilename's optional -p/--prepend flag
smart: appendfilename's optional --smart-prepend flag
#+end_src
** Building the test script ** Building the test script
*** header section *** header section
#+BEGIN_SRC python :tangle test_appendfilename.py #+BEGIN_SRC python :tangle test_appendfilename.py
#!/bin/usr/env python3 #!/bin/usr/env python3
@ -175,219 +157,332 @@ markers =
# author: nbehrnd@yahoo.com # author: nbehrnd@yahoo.com
# license: GPL v3, 2022. # license: GPL v3, 2022.
# date: 2022-01-05 (YYYY-MM-DD) # date: 2022-01-05 (YYYY-MM-DD)
# edit: [2024-10-31 Thu] # edit: [2024-11-05 Tue]
# #
"""Test pad for functions by appendfilename with pytest. """Test pad for functions by appendfilename with pytest.
Written for Python 3.9.9 and pytest 6.2.4 for Python 3 as provided by Initially written for Python 3.9.9 and pytest 6.2.4 and recently update
Linux Debian 12/bookworm, branch testing, this is a programmatic check for Python 3.12.6/pytest 8.3.3, this script provides a programmatic check
of functions offered by appendfilename. Deposit this script in the root of of functions offered by appendfilename. Deposit this script in the root of
the folder fetched and unzipped from PyPi or GitHub. If your system the folder fetched and unzipped from PyPi or GitHub. Create a virtual
includes both legacy Python 2 and Python 3, pytest for Python 3 likely environment for Python, e.g. by
is named pytest-3; otherwise only pytest. Thus, adjust your input on
the CLI accordingly when running either one of
pytest -v test_appendfilename.py ```shell
pytest-3 -v test_appendfilename.py python -m venv sup
```
These instruction initiate a verbose testing (flag -v) reported back to the In the activated virtual environment, ensure the dependencies are met -
CLI.re will be a verbose report to the CLI The script either stops when one of either by `pip install pyreadline3 pytest`, or `pip install -r requirements.txt`
the tests fail (flag -x), or after completion of the test sequence. In both - and launch the tests by
cases, the progress of the ongoing tests is reported to the CLI (flag -v)."""
```shell
python -m pytest
```
As a reminder, the following optional pytest flags may be useful to obtain
a report tailored to your needs:
- `-x` exits right after the first failing test (reported by `E` instead of `.`)
- `-v` provide a more verbose output
- `-s` equally report the test criterion, e.g. the queried file name
"""
import re import re
import os import os
import pytest import shlex
import sys import sys
import subprocess import subprocess
from pathlib import Path from itertools import product
from subprocess import getstatusoutput, getoutput
PROGRAM = str(Path("appendfilename") / "__init__.py") # Cross-platform path import pytest
PROGRAM = os.path.join("appendfilename", "__init__.py") # Cross-platform path
#+end_src #+end_src
*** appendfilename, default position *** appendfilename, default position
Departing with file =test.txt=, appendfile's addition of =example= should Departing with file =test.txt=, appendfile's addition of =example= should
yield =test example.txt=. Testing so far skips the addition of string yield =test example.txt=.
containing spaces, as well as the implicit spacing.
#+begin_src python :tangle test_appendfilename.py #+begin_src python :tangle test_appendfilename.py
# The following section tests the applications default pattern where a string
# is added to the file name, just prior to the file's file extension. The
# permutation of the three arguments and their levels defines 120 tests.
arg1_values = [
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
]
arg2_values = [
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
"--separator '!'",
"--separator '@'",
"--separator '#'",
"--separator '$'",
"--separator '%'",
"--separator '_'",
"--separator '+'",
"--separator '='",
"--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values))
@pytest.mark.default @pytest.mark.default
@pytest.mark.parametrize("arg1", ["test.txt", "2021-12-31_test.txt", @pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
"2021-12-31T18.48.22_test.txt"]) def test_append(arg1, arg2, arg3):
@pytest.mark.parametrize("arg2", ["-t book", "-t book_shelf"])#, """Test default which appends a string just prior file extension
# "--text book", "--text book_shelf"])
#@pytest.mark.parametrize("arg3", [" ", "!", "@", "#", "$", "%", "*", "_", "+",
@pytest.mark.parametrize("arg3", [" ", "!", "@", "#", "$", "%", "_", "+",
"=", "-"])
def test_pattern_s1(arg1, arg2, arg3):
"""Check addition just ahead the file extension.
arg1 the test files to process arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added arg2 the text string to be added
arg3 the explicitly defined text separator (except [a-zA-Z])""" arg3 the separator (at least in Windows 10, do not use `*`)"""
# extract the newly added text information: # create a test file:
text_elements = arg2.split(" ")[1:] with open(arg1, mode="w", encoding="utf-8") as newfile:
text = str(" ".join(text_elements)) newfile.write("This is a place holder.\n")
with open(arg1, mode="w") as newfile: # run the test to be tested:
newfile.write("This is a test file for test_appendfilename.") full_command = ["python", PROGRAM, arg1
] + shlex.split(arg2) + shlex.split(arg3)
subprocess.run(full_command, text = True, check = True)
# Run the command with cross-platform Python executable and file paths # construct the new file name to be tested:
result = subprocess.run( if len(shlex.split(arg3)) == 0:
[sys.executable, PROGRAM, arg1, arg2, f"--separator={arg3}"], separator = " "
capture_output=True, text=True, check=True) else:
separator = shlex.split(arg3)[1]
new_filename = "".join([arg1[:-4], arg3, " ", text, str(".txt")]) new_filename = "".join(
[ arg1[:-4], separator,
shlex.split(arg2)[1], ".txt" ])
print(f"test criterion: {new_filename}") # visible by optional `pytest -s`
# is the new file present?
assert os.path.isfile(new_filename) assert os.path.isfile(new_filename)
# space cleaning # check if the OS can process the new file / space cleaning
os.remove(new_filename) os.remove(new_filename)
assert os.path.isfile(new_filename) is False
#+end_src #+end_src
*** appendfilename, prepend position *** appendfilename, prepend position
Departing with file =test.txt=, appendfile's addition of =example= Departing with file =test.txt=, appendfile's addition of =example=
should yield =example test.txt=. Testing so far skips the should yield =example test.txt=. The 240 tests equally consider the
addition of string containing spaces, as well as the implicit separator between the string added, and the original file name.
spacing.
#+begin_src python :tangle test_appendfilename.py
# The following section is about tests to prepend a user defined string and
# an adjustable separator to the original file name of the file submitted. By
# permutation of the parameter's levels, this defines 240 tests.
arg1_values = [
"test.txt", "2021-12-31_test.txt", "2021-12-31T18.48.22_test.txt"
]
arg2_values = [
"-t book", "-t book_shelf", "--text book", "--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
"--separator '!'",
"--separator '@'",
"--separator '#'",
"--separator '$'",
"--separator '%'",
"--separator '_'",
"--separator '+'",
"--separator '='",
"--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
arg4_values = [
"-p", "--prepend"
]
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values, arg4_values))
#+begin_src python :tangle no
@pytest.mark.prepend @pytest.mark.prepend
@pytest.mark.parametrize("arg1", ["test.txt", "2021-12-31_test.txt", @pytest.mark.parametrize("arg1, arg2, arg3, arg4", test_cases)
"2021-12-31T18.48.22_test.txt", def test_prepend(arg1, arg2, arg3, arg4):
"20211231_test.txt", "2012-12_test.txt", """test to prepend a string to the original file name
"211231_test.txt"])
@pytest.mark.parametrize("arg2", ["-t book", "-t book_shelf",
"--text book", "--text book_shelf"])
@pytest.mark.parametrize("arg3", [" ", "!", "@", "#", "$", "%", "*", "_", "+",
"=", "-"])
@pytest.mark.parametrize("arg4", ["-p", "--prepend"])
def test_pattern_s2(arg1, arg2, arg3, arg4):
"""Check addition just ahead the file extension.
arg1 the test files to process arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added arg2 the text string to be added
arg3 the explicitly defined text separator (except [a-zA-Z]) arg3 the separator (at least in Windows 10, do not use `*`)
arg4 use either of two forms of the prepend flag.""" arg4 either short of long form to introduce the string as leading """
# extract the newly added text information: # create a test file:
text_elements = arg2.split(" ")[1:] with open(arg1, mode="w", encoding="utf-8") as newfile:
text = str(" ".join(text_elements)) newfile.write("This is a place holder.\n")
with open(arg1, mode="w") as newfile: # run the test to be tested:
newfile.write("This is a test file for test_appendfilename.") full_command = [
"python", PROGRAM, arg1
] + shlex.split(arg2) + shlex.split(arg3) + shlex.split(arg4)
subprocess.run(full_command, text = True, check = True)
test = getoutput(f"python3 {PROGRAM} {arg1} {arg2} --separator={arg3} {arg4}") # construct the new file name to be tested:
if len(shlex.split(arg3)) == 0:
separator = " "
else:
separator = shlex.split(arg3)[1]
new_filename = "".join([text, arg3, arg1]) new_filename = "".join( [ shlex.split(arg2)[1], separator, arg1 ] )
print(f"test criterion: {new_filename}") # visible by optional `pytest -s`
# is the new file present?
assert os.path.isfile(new_filename) assert os.path.isfile(new_filename)
# check if the OS can process the new file / space cleaning
os.remove(new_filename) os.remove(new_filename)
assert os.path.isfile(new_filename) is False assert os.path.isfile(new_filename) is False
#+end_src #+end_src
*** appendfilename, smart prepend position *** appendfilename, smart prepend position
If a file has a leading time stamp like =YYYY-MM-DD_=, or
=YYYY-MM-DDTHH:MM:SS_=, than a smart addition of the text to the
file name should follow this. So far, the tests recognize only
these two pattern issued by =date2name=, or the absence of such.
#+begin_src python :tangle no Here, the additional string follows the time stamp, and leads
the rest of the file's file name. Of five patterns provided by
=date2name=, only =--withtime= and the default YYYY-MM-DD are
checked. The other three (=--compact=, =--month=, and =--short=)
are muted for their pattern still different to the other two.
Equally see [[https://github.com/novoid/appendfilename/issues/15]]
and [[https://github.com/novoid/appendfilename/issues/16]].
The permutation of the parameter's active levels define 8 tests.
#+begin_src python :tangle test_appendfilename.py
# This section tests the insertion of a string into the file's file name
# just after the file's time or date stamp as provided `date2name`.
arg1_values = [
"2021-12-31T18.48.22_test.txt",
"2021-12-31_test.txt",
# "20211231_test.txt", # by now `20211231_test.txt` -> 20211231_test ping.txt
# "2021-12_test.txt", # by now `2021-12_test.txt` -> `2021-12_test ping.txt`
# "211231_test.txt" # by now `211231_test.txt` -> `211231_test ping.txt`
]
arg2_values = [
"-t book",
"-t book_shelf",
"--text book",
"--text book_shelf"
]
arg3_values = [
"", # i.e. fall back to default single space
# "--separator '!'",
# "--separator '@'",
# "--separator '#'",
# "--separator '$'",
# "--separator '%'",
# "--separator '_'",
# "--separator '+'",
# "--separator '='",
# "--separator '-'"
]
# Note: The check with pytest and `*` as separator in Windows 10 fails.
# Contrasting to Linux Debian 13, a `pytest` in Windows 10 revealed every
# of these special characters can not safely used as an additional separator.
# create the permutations:
test_cases = list(product(arg1_values, arg2_values, arg3_values))
@pytest.mark.smart @pytest.mark.smart
@pytest.mark.parametrize("arg1", ["test.txt", "2021-12-31_test.txt", @pytest.mark.parametrize("arg1, arg2, arg3", test_cases)
"2021-12-31T18.48.22_test.txt", "20211231_test.txt", def test_smart_prepend(arg1, arg2, arg3):
"2021-12_test.txt", "211231_test.txt"]) """test the insertion of a new string just past the time stamp
@pytest.mark.parametrize("arg2", ["-t book", "-t book_shelf",
"--text book", "--text book_shelf"])
@pytest.mark.parametrize("arg3", [" " , "#", "!", "@", "#", "$", "%", "*", "_", "+",
"=", "-"])
def test_pattern_s3_02(arg1, arg2, arg3):
"""Check addition retaining time stamp on leading position.
arg1 the test files to process arg1 the test file to process, partly inspired by `date2name`
arg2 the text string to be added arg2 the text string to be added
arg3 the explicitly defined text separator (except [a-zA-Z]).""" arg3 the separator (at least in Windows 10, do not use `*`
"""
time_stamp = ""
time_stamp_separator = ""
old_filename_no_timestamp = ""
# extract the newly added text information: # create a test file:
text_elements = arg2.split(" ")[1:] with open(arg1, mode="w", encoding="utf-8") as newfile:
text = str(" ".join(text_elements)) newfile.write("this is a placeholder\n")
with open(arg1, mode="w") as newfile: #run `appendfilename` on this test file
newfile.write("This is a test file for test_appendfilename.") run_appendfilename = " ".join(
["python", PROGRAM, arg1, arg2, arg3, " --smart-prepend"])
subprocess.run(run_appendfilename, shell=True, check = True)
test = getoutput(f"python3 {PROGRAM} {arg1} {arg2} --separator={arg3} --smart-prepend") # construct the new file name to be testedt:
old_filename = arg1
# analysis section:
old_filename = str(arg1)
# test pattern issued by date2name vs. other pattern
# default (YYYY-MM-DD)
# --withtime (YYYY-MM-DDTHH.MM.SS)
# --compact (YYYYMMDD)
# --month (YYYY-MM)
# --short (YYMMDD)
if (re.search("^\d{4}-[012]\d-[0-3]\d_", old_filename) or
re.search('^\d{4}-[012]\d-[0-3]\dT[012]\d\.[0-5]\d\.[0-5]\d_', old_filename) or
re.search("^\d{4}[012]\d[0-3]\d_", old_filename) or
re.search("^\d{4}-[012]\d_", old_filename) or
re.search("^\d{2}[012]\d[0-3]\d_", old_filename)):
if re.search("^\d{4}-\d{2}-\d{2}_", old_filename):
# if (running date2name in default mode) then .true.
time_stamp = old_filename[:10]
time_stamp_separator = old_filename[10]
file_extension = old_filename.split(".")[-1]
old_filename_no_timestamp = old_filename[11:]
elif re.search('^\d{4}-\d{2}-\d{2}T\d{2}\.\d{2}\.\d{2}_', old_filename):
# if (running date2name --withtime) then .true.
time_stamp = old_filename[:19]
time_stamp_separator = old_filename[19]
file_extension = old_filename.split(".")[-1]
old_filename_no_timestamp = old_filename[20:]
elif re.search("^\d{4}\d{2}\d{2}_", old_filename):
# if (running date2name --compact) then .true.
time_stamp = old_filename[:8]
time_stamp_separator = old_filename[8]
file_extension = old_filename.split(".")[-1]
old_filename_no_timestamp = old_filename[9:]
elif re.search("^\d{4}-\d{2}_", old_filename):
# if (running date2name --month) then .true.
time_stamp = old_filename[:7]
time_stamp_separator = old_filename[7]
file_extension = old_filename.split(".")[-1]
old_filename_no_timestamp = old_filename[8:]
elif re.search("^\d{4}\d{2}\d{2}_", old_filename):
# if (running date2name --short) then .true.
time_stamp = old_filename[:6]
time_stamp_separator = old_filename[6]
file_extension = old_filename.split(".")[-1]
old_filename_no_timestamp = old_filename[7:]
stem_elements = old_filename_no_timestamp.split(".")[:-1]
stem = ".".join(stem_elements)
new_filename = "".join([time_stamp, arg3, text, arg3, stem, str("."), file_extension])
assert os.path.isfile(new_filename)
os.remove(new_filename)
assert os.path.isfile(new_filename) is False
# account for the implicit separator, i.e. the single space:
if len(shlex.split(arg3)) == 0:
separator = " "
else: else:
# within the scope set, a file which did not pass date2name earlier separator = shlex.split(arg3)[1]
new_filename = "".join([text, arg3, old_filename])
assert os.path.isfile(new_filename)
os.remove(new_filename) # Time stamps `date2name` provides can be either one of five formats
assert os.path.isfile(new_filename) is False #
# YYYY-MM-DDTHH.MM.SS `--withtime`
# YYYY-MM-DD default
# YYYYMMDD `--compact`
# YYYY-MM `--month`
# YYMMDD `--short`
# Currently, one observes two patterns by `appendfilename`: one which
# substitutes the separator by `date2name`, the other which retains it.
# Note patterns `compact`, `month`, and `short`, currently append the
# additional string rather than smartly prepend after the date stamp --
# for now, these three are not tested. Equally see discussions 15 and 16,
# https://github.com/novoid/appendfilename/issues/15
# https://github.com/novoid/appendfilename/issues/16
# pattern `--with-time`
if re.search(r"^\d{4}-[012]\d-[0-3]\dT[012]\d\.[0-5]\d\.[0-5]\d", old_filename):
time_stamp = old_filename[:19]
time_stamp_separator = old_filename[19]
old_filename_no_timestamp = old_filename[20:]
# default pattern
elif re.search(r"^\d{4}-[012]\d-[0-3]\d", old_filename):
time_stamp = old_filename[:10]
time_stamp_separator = old_filename[10]
old_filename_no_timestamp = old_filename[11:]
# pattern `--compact` # currently fails
elif re.search(r"^\d{4}[012]\d[0-3]\d", old_filename):
time_stamp = old_filename[:8]
time_stamp_separator = old_filename[8]
old_filename_no_timestamp = old_filename[9:]
# pattern `--month` # currently fails
elif re.search(r"^\d{4}-[012]\d", old_filename):
time_stamp = old_filename[:7]
time_stamp_separator = old_filename[7]
old_filename_no_timestamp = old_filename[8:]
# pattern `--short` # currently fails
elif re.search(r"^\d{4}[012]\d[012]\d", old_filename):
time_stamp = old_filename[:6]
time_stamp_separator = old_filename[6]
old_filename_no_timestamp = old_filename[7:]
new_filename = "".join([time_stamp, #time_stamp_separator,
separator, shlex.split(arg2)[1], separator,
old_filename_no_timestamp ])
# is the new file present?
print("\nnew_filename") # optional check for `pytest -s`
print(new_filename)
assert os.path.isfile(new_filename)
# check if the IS can process the new file / space cleaning
os.remove(new_filename)
assert os.path.isfile(new_filename) is False
#+end_src #+end_src