From cb9447f94976f6f2ed7a8026697fa070db69dab8 Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Mon, 13 Apr 2026 16:37:18 -0400 Subject: [PATCH 1/2] test: disable stderr capturing when running pytest --- tests/basic_test_case.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/basic_test_case.py b/tests/basic_test_case.py index 0bae361b..50f18703 100644 --- a/tests/basic_test_case.py +++ b/tests/basic_test_case.py @@ -87,8 +87,10 @@ def silence_c_stderr(): address our narrow needs, namely to silence stderr in a context manager. """ - if os.name == "nt" and sys.version_info < (3, 10): - yield # work around instability for this on Windows with Py 3.8/3.9 + if "pytest" in sys.modules or os.name == "nt" and sys.version_info < (3, 10): + # Incompatible and pointless with pytest since it captures all output + # Also work around instability for this on Windows with Py 3.8/3.9 + yield else: stderr_fileno = sys.stderr.fileno() stderr_save = os.dup(stderr_fileno) From e8fd9247749e52158e2470af510dff45f7a2ef9e Mon Sep 17 00:00:00 2001 From: Eric Joanis Date: Thu, 16 Apr 2026 10:37:47 -0400 Subject: [PATCH 2/2] build(deps): make readalongs compatible with recent versions of click stderr is no longer mixed with stdout, but it is always included in result.output with old and new versions of click --- pyproject.toml | 2 +- readalongs/cli.py | 1 + tests/test_align_cli.py | 27 ++++++++++++++------------- tests/test_make_xml_cli.py | 10 +++++----- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ac25978..468fff92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ classifiers = [ dependencies = [ "chevron==0.14.0", - "click>=8.0.4,<8.2.0", + "click>=8.0.4", "coloredlogs>=10.0", "fastapi>=0.103.0", "g2p>=2.2.0, <3", diff --git a/readalongs/cli.py b/readalongs/cli.py index 14dcb136..643d2070 100644 --- a/readalongs/cli.py +++ b/readalongs/cli.py @@ -147,6 +147,7 @@ def cli(): "--config", type=click.Path(exists=True), help="Use ReadAlong-Studio configuration file (in JSON format)", + default=None, ) @click.option( "-o", diff --git a/tests/test_align_cli.py b/tests/test_align_cli.py index 836dce69..39708f04 100755 --- a/tests/test_align_cli.py +++ b/tests/test_align_cli.py @@ -96,11 +96,11 @@ def test_invoke_align(self) -> None: (output / "www/assets/image-for-page1.jpg").exists(), "alignment with image files should have copied image-for-page1.jpg to assets", ) - self.assertIn("image-for-page2.jpg is accessible ", results.stdout) + self.assertIn("image-for-page2.jpg is accessible ", results.output) os.unlink("image-for-page1.jpg") self.assertFalse(exists("image-for-page1.jpg")) - self.assertRegex(results.stdout, "Align mode .* succeeded for sequence 0.") - # print(results.stdout) + self.assertRegex(results.output, "Align mode .* succeeded for sequence 0.") + # print(results.output) # Move the alignment output to compare with further down # We cannot just output to a different name because changing the output file name @@ -126,7 +126,7 @@ def test_invoke_align(self) -> None: ], ) self.assertEqual(results_dna.exit_code, 0) - # print(results_dna.stdout) + # print(results_dna.output) self.assertTrue( (output / "www/output.readalong").exists(), "successful alignment with DNA should have created output.readalong", @@ -135,13 +135,13 @@ def test_invoke_align(self) -> None: (output / "output.xhtml").exists(), "successful alignment with -o xhtml should have created output.xhtml", ) - self.assertIn("Please copy image-for-page1.jpg to ", results_dna.stdout) + self.assertIn("Please copy image-for-page1.jpg to ", results_dna.output) self.assertFalse( (output / "www/assets/image-for-page1.jpg").exists(), "image-for-page1.jpg was not on disk, cannot have been copied", ) self.assertIn( - "Align mode moderate succeeded for sequence 0.", results_dna.stdout + "Align mode moderate succeeded for sequence 0.", results_dna.output ) # We test error situations in the same test case, since we reuse the same outputs @@ -153,6 +153,7 @@ def test_invoke_align(self) -> None: str(output), ], ) + print("dir(result)", dir(results_output_exists)) self.assertNotEqual(results_output_exists.exit_code, 0) self.assertIn( "already exists, use -f to overwrite", results_output_exists.output @@ -237,10 +238,10 @@ def test_langs_cmd(self): """Validates that readalongs langs lists all in-langs that can map to eng-arpabet""" results = self.runner.invoke(langs) self.assertEqual(results.exit_code, 0) - self.assertIn("crg-tmd", results.stdout) - self.assertIn("crg-dv ", results.stdout) - self.assertNotIn("crg ", results.stdout) - self.assertNotIn("fn-unicode", results.stdout) + self.assertIn("crg-tmd", results.output) + self.assertIn("crg-dv ", results.output) + self.assertNotIn("crg ", results.output) + self.assertNotIn("fn-unicode", results.output) def test_align_english(self): """Validates that the lexicon-based g2p works for English language alignment""" @@ -310,7 +311,7 @@ def test_invalid_config(self): join(self.tempdir, "out-invalid-config-1"), ], ) - self.assertIn("must be in JSON format", result.stdout) + self.assertIn("must be in JSON format", result.output) # --config parameters needs to contain valid json, test with garbage config_file = join(self.tempdir, "bad-config.json") @@ -326,7 +327,7 @@ def test_invalid_config(self): join(self.tempdir, "out-invalid-config-2"), ], ) - self.assertIn("is not in valid JSON format", result.stdout) + self.assertIn("is not in valid JSON format", result.output) def test_bad_anchors(self): """Make sure invalid anchors yield appropriate errors""" @@ -356,7 +357,7 @@ def test_bad_anchors(self): "Could not parse all anchors", "Aborting.", ]: - self.assertIn(msg, bad_anchors_result.stdout) + self.assertIn(msg, bad_anchors_result.output) def test_misc_align_errors(self): """Test calling readalongs align with misc CLI errors""" diff --git a/tests/test_make_xml_cli.py b/tests/test_make_xml_cli.py index 1c4c0fa9..aa5ab889 100755 --- a/tests/test_make_xml_cli.py +++ b/tests/test_make_xml_cli.py @@ -47,7 +47,7 @@ def test_invoke_make_xml(self): ["-l", "atj", "-d", self.empty_file, os.path.join(self.tempdir, "delme")], ) self.assertEqual(results.exit_code, 0) - self.assertRegex(results.stdout, "Running readalongs make-xml") + self.assertRegex(results.output, "Running readalongs make-xml") def test_no_lang(self): """Error case: readalongs make-xml without the mandatory -l switch""" @@ -55,13 +55,13 @@ def test_no_lang(self): make_xml, [self.empty_file, self.empty_file + ".readalong"] ) self.assertNotEqual(results.exit_code, 0) - self.assertRegex(results.stdout, "Missing.*language") + self.assertRegex(results.output, "Missing.*language") def test_inputfile_not_exist(self): """Error case: input file does not exist""" results = self.runner.invoke(make_xml, "-l atj /file/does/not/exist delme") self.assertNotEqual(results.exit_code, 0) - self.assertRegex(results.stdout, "No such file or directory") + self.assertRegex(results.output, "No such file or directory") def test_outputfile_exists(self): """Existing output file should not be overwritten by readalongs make-xml by default""" @@ -74,7 +74,7 @@ def test_outputfile_exists(self): ["-l", "atj", self.empty_file, os.path.join(self.tempdir, "exists")], ) self.assertNotEqual(results.exit_code, 0) - self.assertRegex(results.stdout, "exists.*overwrite") + self.assertRegex(results.output, "exists.*overwrite") def test_output_exists(self): """Make sure readalongs make-xml create the expected output file""" @@ -124,7 +124,7 @@ def test_generate_output_name(self): # LOGGER.warning("Output: {}".format(results.output)) # LOGGER.warning("Exception: {}".format(results.exception)) self.assertEqual(results.exit_code, 0) - self.assertRegex(results.stdout, "Wrote.*someinput[.]readalong") + self.assertRegex(results.output, "Wrote.*someinput[.]readalong") self.assertTrue( os.path.exists(os.path.join(self.tempdir, "someinput.readalong")) )