Repository: jackdewinter/pyscan

Issue:
Report and publish directories need to be configurable.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 02d753c..475e079 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Project Summarizer is a tool used to summarize various files produced by other t
 This tool is intended to be executed after running a test script or a build script.
 The benefit to using the Project Summarizer tool is a quick summary of more terse information provided by other tools.
 The goal is to provide the minimum level of feedback on changes to the project, avoiding a more costly lookup of the summarized information.
-If that goal is met, then a simple look at that summarized information can replace a more costly lookup, such as having to switch focus to a locally hosted web page to determine impact.
+If that goal is met, then a simple look at that summarized information can replace a more costly lookup, such as having to switch focus to a locally hosted web page to figure out the impact of a change.
 
 Our hope is that we can help developers achieve that goal at least 50 percent of the time.
 
@@ -60,7 +60,7 @@ While the report files are not intended for human consumption, their summarized
 
 While complete information on the current state of the project is useful, our development team finds that most often that they are looking for what has changed.
 That is where the Project Summarizer tool shines.
-But, to understand what has changed, a benchmark or snapshot of the previous "current" state must be placed somewhere.
+But, to understand what has changed, a benchmark or snapshot of an earlier "current" state must be placed somewhere.
 For the Project Summarizer tool, those summary files in the `report` directory are *published* to the `publish` directory using the `--publish` argument.
 In our team, publishing is performed as the last action before committing changes to a project's repository.
 The intent of that action is that we can always determine what changes have occurred since the last commit.
@@ -71,7 +71,7 @@ With that argument present, the summaries will not only display values that have
 If adding, removing, or enabling tests, this is useful to make sure that the count of changed tests is as expected.
 If making any changes to the code that the tests are covering, this is useful to see what effect the change has on code coverage metrics.
 
-Our team uses test driven development and maintains a high code coverage metric for all our projects.
+Our team uses test driven development and keeps a high code coverage metric for all our projects.
 The Project Summarizer tool allows us to see the impact of our current changes on the current existing tests, enabled and disabled.
 It also allows us to keep track of the impact of any code changes on our coverage metrics.
 With both summaries, if the reported information is outside of our expectations, we can then look at the more comprehensive reports to find that needed information.
@@ -88,7 +88,7 @@ report/
 
 ## Sample Output
 
-These samples were captured against the Project Summarizer project itself and have not been modified.
+These samples were captured against the Project Summarizer project itself and have not been changed.
 
 ### Full Output
 
@@ -172,13 +172,13 @@ Test Coverage Summary
 
 Our team's development is primarily done on Windows systems.
 As such, any examples that we present will typically be Windows CMD scripts.
-We have a project note to provide Bash scripts for the project soon.
+We have a project note to supply Bash scripts for the project soon.
 
 ## Using This Project On Itself - Setup
 
 For the Project Summarizer project itself, there is a `ptest.cmd` script that allows for various modes to be used in executing PyTest against the project.
 These different modes allow for more focused testing depending on the needs of the developer at the time.
-Those different modes use environment variables to specify how to execute PyTest, and for the purpose of this documentation, those variables are ignored to provide a clearer picture of how the Project Summarizer tool works.
+Those different modes use environment variables to specify how to execute PyTest, and for the purpose of this documentation, those variables are ignored to supply a clearer picture of how the Project Summarizer tool works.
 
 With those other environment variables out of the way, the heart of the script is the `PYTEST_ARGS` environment variable.
 Under normal operation, that environment variable is set to:
@@ -220,7 +220,7 @@ pipenv run %PTEST_PROJECT_SUMMARIZER_SCRIPT_PATH% %PTEST_PROJECT_SUMMARIZER_OPTI
 
 As the summarizer is being used in the same project as it is created in, the `PTEST_PROJECT_SUMMARIZER_SCRIPT_PATH` variable is set to point to the project's `main.py` module.
 Normally, that value would be the name of the Python package `project_summarizer`.
-The `PTEST_PROJECT_SUMMARIZER_OPTIONS` variable contains the `--only-changes` argument by default but can be turned off if desired.
+The `PTEST_PROJECT_SUMMARIZER_OPTIONS` variable holds the `--only-changes` argument by default but can be turned off if desired.
 The `PTEST_TEST_RESULTS_PATH` variable is set to `report\tests.xml` to correspond with the `--junitxml=report/tests.xml` argument passed to PyTest.
 The `PTEST_TEST_COVERAGE_PATH` variable is set to `report\coverage.xml` to match the `--cov-report xml:report/coverage.xml` argument passed to PyTest.
 
@@ -233,17 +233,37 @@ All the samples in the [Sample Output](#sample-output) section are generated usi
 
 - used as part of the normal development process, `ptest.cmd --publish` was previously invoked to benchmark the summaries for the last commit
   - this effectively invoked `project_summarizer --publish`
-- `ptest -c -f` was invoked to run tests with coverage and provide the [Full Output sample](#full-output)
+- `ptest -c -f` was invoked to run tests with coverage and supply the [Full Output sample](#full-output)
   - this ran PyTest with coverage enabled, and effectively invoked `project_summarizer --junit report\tests.xml --cobertura report\coverage.xml`
 - `ptest -c` was invoked to run tests with coverage and present the [No Change sample](#only-output-changes-with-no-changes)
   - this ran PyTest with coverage enabled, and effectively invoked `project_summarizer --only-changes --junit report\tests.xml --cobertura report\coverage.xml`
 - `ptest -c` was invoked to run tests with coverage and present the [Expected Change sample](#expected-change)
   - same as above, but with the test "missing", it reported that test as not being executed and reported the coverage that was missing
 
+## Advanced Features
+
+### Quiet Mode and Output Character Columns
+
+After use in different real-world scenarios, the ability to maintain better control on the report output became more apparent.
+To that extent, two new arguments were added to the command line: `--quiet` and `--columns`.
+The `--quiet` argument instructs the report summarizers to only generate their output files, and not to generate any console output.
+This feature is useful on servers where the tool is still executed to keep things coordinated with developer workflows, but the console output is not important.
+In those cases where the output on servers is relevant, the `--columns` argument is useful in specifying the number of character columns to use for the output.
+The integer value passed after the `--columns` argument is passed to the table formatting package that is suggested for plugins: `columnar`.
+In the past, when the tool has been run on server environments, there have been situations where the `columnar` package's determination of number of output columns has been wrong or had returned `0`.
+By using this setting, those situations can be sidestepped by hardwiring the number of columns from the command line.
+
+### Setting The Reporting and Publishing Directories
+
+As mentioned above, the `report` directory is used to generate summary reports in, and those reports can then be published into the `publish` directory.
+If different names are wanted for these two directories, they can be set with the `--report-dir` argument and the `--publish-dir` argument.
+Note that the report directory, either the default `report` or the value supplied with the `--report-dir` argument, must exist before this tool is called.
+However, the publish directory, either the default `publish` or the value supplied with the `--publish-dir` argument, will be created if it does not exist.
+
 ## Future Goals
 
-The initial 0.5.0 release is to get this project on the board.
-Once that is done, we have plans to implement an extension mechanism to provide for customized summaries.
+The 0.5.0 releases are to get this project on the board.
+Once that is done, we have plans to implement an extension mechanism to support customized summaries.
 
 ## When Did Things Change?
 
diff --git a/project_summarizer/cobertura_plugin.py b/project_summarizer/cobertura_plugin.py
index 34f6759..f5160e8 100644
--- a/project_summarizer/cobertura_plugin.py
+++ b/project_summarizer/cobertura_plugin.py
@@ -26,7 +26,15 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
 
     def __init__(self):
         super().__init__()
-        self.__output_path = os.path.join(self.REPORT_PUBLISH_PATH, "coverage.json")
+        self.__output_path = None
+        self.__context = None
+
+    def set_context(self, context):
+        """
+        Set the context for the plugins.
+        """
+        self.__context = context
+        self.__output_path = os.path.join(self.__context.report_dir, "coverage.json")
 
     def get_output_path(self):
         """
@@ -46,7 +54,7 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
             metavar="path",
             action="store",
             default="",
-            help="source file name for cobertura test coverage reporting",
+            help="Source file name for cobertura test coverage reporting.",
         )
         return (
             CoberturaPlugin.__COMMAND_LINE_ARGUMENT,
@@ -58,7 +66,9 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
 
         line_count = 0
         combined_lines = None
-        with open(report_file, "rt", encoding="utf-8") as infile:
+        with open(
+            report_file, "rt", encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING
+        ) as infile:
             next_line = infile.readline()
             while next_line and next_line.strip() != "<sources>" and line_count < 10:
                 combined_lines = (
@@ -92,9 +102,7 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
 
         if column_width:
             published_coverage_summary_path = (
-                ProjectSummarizerPlugin.compute_published_path_to_file(
-                    self.__output_path
-                )
+                self.__context.compute_published_path_to_file(self.__output_path)
             )
             loaded_stats = self.__load_coverage_results_summary_file(
                 published_coverage_summary_path
@@ -171,7 +179,9 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
         ):
             try:
                 with open(
-                    os.path.abspath(test_results_to_load), "r", encoding="utf-8"
+                    os.path.abspath(test_results_to_load),
+                    "r",
+                    encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING,
                 ) as infile:
                     results_dictionary = json.load(infile)
             except json.decoder.JSONDecodeError as ex:
@@ -319,14 +329,16 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
             column_value = next_row[primary_column_index].rjust(column_maximums[0], " ")
         if column_maximums[1] != 0:
             if next_row[primary_column_index + 1] == "-":
-                column_value = (f'{column_value} ' + " ".rjust(column_maximums[1] + 2, " "))
+                column_value = f"{column_value} " + " ".rjust(
+                    column_maximums[1] + 2, " "
+                )
             else:
                 partial_value = next_row[primary_column_index + 1]
                 has_changes = partial_value.startswith("+") or partial_value.startswith(
                     "-"
                 )
                 partial_value = partial_value.rjust(column_maximums[1], " ")
-                column_value = f'{column_value} ({partial_value})'
+                column_value = f"{column_value} ({partial_value})"
         return column_value, has_changes
 
     @classmethod
@@ -405,7 +417,7 @@ class CoberturaPlugin(ProjectSummarizerPlugin):
             and delta_value != "0.00"
             and not delta_value.startswith("-")
         ):
-            delta_value = f'+{delta_value}'
+            delta_value = f"+{delta_value}"
         new_row.append(delta_value)
 
     # pylint: enable=unused-private-member
diff --git a/project_summarizer/junit_plugin.py b/project_summarizer/junit_plugin.py
index 34f9009..d9d0ab3 100644
--- a/project_summarizer/junit_plugin.py
+++ b/project_summarizer/junit_plugin.py
@@ -23,7 +23,17 @@ class JUnitPlugin(ProjectSummarizerPlugin):
 
     def __init__(self):
         super().__init__()
-        self.__output_path = os.path.join(self.REPORT_PUBLISH_PATH, "test-results.json")
+        self.__output_path = None
+        self.__context = None
+
+    def set_context(self, context):
+        """
+        Set the context for the plugins.
+        """
+        self.__context = context
+        self.__output_path = os.path.join(
+            self.__context.report_dir, "test-results.json"
+        )
 
     def get_output_path(self):
         """
@@ -43,7 +53,7 @@ class JUnitPlugin(ProjectSummarizerPlugin):
             metavar="path",
             action="store",
             default="",
-            help="source file name for junit test result reporting",
+            help="Source file name for junit test result reporting.",
         )
         return (
             JUnitPlugin.__COMMAND_LINE_ARGUMENT,
@@ -64,7 +74,7 @@ class JUnitPlugin(ProjectSummarizerPlugin):
         self.save_summary_file(self.__output_path, new_stats, "test report")
 
         if column_width:
-            published_test_summary_path = self.compute_published_path_to_file(
+            published_test_summary_path = self.__context.compute_published_path_to_file(
                 self.__output_path
             )
             loaded_stats, loaded_totals = self.load_test_results_summary_file(
@@ -146,7 +156,9 @@ class JUnitPlugin(ProjectSummarizerPlugin):
         ):
             try:
                 with open(
-                    os.path.abspath(test_results_to_load), "r", encoding="utf-8"
+                    os.path.abspath(test_results_to_load),
+                    "r",
+                    encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING,
                 ) as infile:
                     results_dictionary = json.load(infile)
             except json.decoder.JSONDecodeError as ex:
diff --git a/project_summarizer/main.py b/project_summarizer/main.py
index 56ad16e..dec54af 100644
--- a/project_summarizer/main.py
+++ b/project_summarizer/main.py
@@ -10,6 +10,7 @@ from shutil import copyfile
 from project_summarizer.cobertura_plugin import CoberturaPlugin
 from project_summarizer.junit_plugin import JUnitPlugin
 from project_summarizer.project_summarizer_plugin import ProjectSummarizerPlugin
+from project_summarizer.summarize_context import SummarizeContext
 
 
 # pylint: disable=too-few-public-methods
@@ -23,7 +24,6 @@ class ProjectSummarizer:
 
     def __init__(self):
         self.__version_number = ProjectSummarizer.__get_semantic_version()
-        self.test_summary_publish_path = ProjectSummarizerPlugin.SUMMARY_PUBLISH_PATH
         self.debug = False
         self.__available_plugins = None
         self.__plugin_argument_names = {}
@@ -63,47 +63,71 @@ class ProjectSummarizer:
                 break
 
         if not are_plugin_arguments_present:
+            print(
+                "Error: Either --publish or one of the reporting arguments mush be specified."
+            )
             parser.print_help()
             sys.exit(2)
 
     def __parse_arguments(self):
         parser = argparse.ArgumentParser(
-            description="Summarize Python files.", allow_abbrev=False
+            description="Summarize Python files.", allow_abbrev=False, add_help=False
+        )
+        parser.add_argument(
+            "-h", "--help", action="help", help="Show this help message and exit."
         )
-
         parser.add_argument(
             "--version",
             action="version",
             version=f"%(prog)s {self.__version_number}",
+            help="Show program's version number and exit.",
         )
+        parser.add_argument(
+            "--report-dir",
+            dest="report_dir",
+            action="store",
+            default="report",
+            help="Directory to generate the summary reports in.",
+            type=ProjectSummarizer.__verify_directory_exists,
+        )
+        parser.add_argument(
+            "--publish-dir",
+            dest="publish_dir",
+            action="store",
+            default=None,
+            help="Directory to publish the summary reports to.",
+            type=ProjectSummarizer.__verify_directory_exists,
+        )
+
         self.__add_command_line_arguments_for_plugins(parser)
+
         parser.add_argument(
             "--only-changes",
             dest="only_changes",
             action="store_true",
             default=False,
-            help="only_changes",
+            help="Only the summary items that have changed are displayed in the console summary.",
         )
         parser.add_argument(
             "--publish",
             dest="publish_summaries",
             action="store_true",
             default=False,
-            help="publish",
+            help="Publish the summaries to the publish directory and exit.",
         )
         parser.add_argument(
             "--quiet",
             dest="quiet_mode",
             action="store_true",
             default=False,
-            help="quiet_mode",
+            help="The report summary files will be generated, but no summary will be output to the console.",
         )
         parser.add_argument(
             "--columns",
             dest="display_columns",
             action="store",
             default=-1,
-            help="display_columns",
+            help="Specifies the number of character columns to use in the console summary.",
             type=ProjectSummarizer.__verify_display_columns,
         )
 
@@ -118,38 +142,41 @@ class ProjectSummarizer:
             argument_as_integer < ProjectSummarizer.__MINIMUM_DISPLAY_COLUMNS
             or argument_as_integer > ProjectSummarizer.__MAXIMUM_DISPLAY_COLUMNS
         ):
-            raise ValueError(
+            raise argparse.ArgumentTypeError(
                 f"Value '{argument}' is not an integer between "
                 + f"between {ProjectSummarizer.__MINIMUM_DISPLAY_COLUMNS} "
                 + f"and {ProjectSummarizer.__MAXIMUM_DISPLAY_COLUMNS}."
             )
         return argument_as_integer
 
-    def __publish_file(self, file_to_publish):
-        if not os.path.exists(self.test_summary_publish_path):
-            print(
-                f"Publish directory '{self.test_summary_publish_path}' does not exist.  Creating."
+    @staticmethod
+    def __verify_directory_exists(argument):
+        report_argument = argument.replace("\\\\", "\\")
+        if not os.path.exists(argument):
+            raise argparse.ArgumentTypeError(
+                f"Path '{report_argument}' does not exist."
             )
-            os.makedirs(self.test_summary_publish_path)
-        elif not os.path.isdir(self.test_summary_publish_path):
-            print(
-                f"Publish directory '{self.test_summary_publish_path}' already exists, but as a file."
+        if not os.path.isdir(argument):
+            raise argparse.ArgumentTypeError(
+                f"Path '{report_argument}' is not an existing directory."
             )
-            sys.exit(1)
+        return argument
 
+    @classmethod
+    def __publish_file(cls, file_to_publish, context):
         if os.path.exists(file_to_publish):
             try:
+                publish_path = context.compute_published_path_to_file(file_to_publish)
                 copyfile(
                     file_to_publish,
-                    ProjectSummarizerPlugin.compute_published_path_to_file(
-                        file_to_publish
-                    ),
+                    publish_path,
                 )
+                print(f"Published: {publish_path}")
             except IOError as ex:
                 print(f"Publishing file '{file_to_publish}' failed ({ex}).")
                 sys.exit(1)
 
-    def __publish_summaries(self):
+    def __publish_summaries(self, context):
         """
         Respond to a request to publish any existing summaries.
         """
@@ -165,8 +192,19 @@ class ProjectSummarizer:
                 sys.exit(1)
             valid_paths.append(plugin_output_path)
 
+        if not os.path.exists(context.publish_dir):
+            print(
+                f"Publish directory '{context.publish_dir}' does not exist.  Creating."
+            )
+            os.makedirs(context.publish_dir)
+        elif not os.path.isdir(context.publish_dir):
+            print(
+                f"Publish directory '{context.publish_dir}' already exists, but as a file."
+            )
+            sys.exit(1)
+
         for plugin_output_path in valid_paths:
-            self.__publish_file(plugin_output_path)
+            self.__publish_file(plugin_output_path, context)
 
     def __create_summaries(self, args):
         arguments_as_dictionary = vars(args)
@@ -193,11 +231,23 @@ class ProjectSummarizer:
 
         args = self.__parse_arguments()
 
+        context = SummarizeContext(
+            report_dir=args.report_dir
+            or ProjectSummarizerPlugin.DEFAULT_REPORT_PUBLISH_PATH,
+            publish_dir=args.publish_dir
+            or ProjectSummarizerPlugin.DEFAULT_SUMMARY_PUBLISH_PATH,
+        )
+
+        for next_plugin in self.__available_plugins:
+            next_plugin.set_context(context)
+        # report_dir
+
         if args.publish_summaries:
-            self.__publish_summaries()
+            self.__publish_summaries(context)
             sys.exit(0)
 
         self.__create_summaries(args)
+        sys.exit(0)
 
 
 if __name__ == "__main__":
diff --git a/project_summarizer/project_summarizer_plugin.py b/project_summarizer/project_summarizer_plugin.py
index 3e4fa1a..334b63f 100644
--- a/project_summarizer/project_summarizer_plugin.py
+++ b/project_summarizer/project_summarizer_plugin.py
@@ -16,12 +16,21 @@ class ProjectSummarizerPlugin(ABC):
     Base class for all project_summarizer plugins.
     """
 
-    SUMMARY_PUBLISH_PATH = "publish"
-    REPORT_PUBLISH_PATH = "report"
+    DEFAULT_SUMMARY_PUBLISH_PATH = "publish"
+    DEFAULT_REPORT_PUBLISH_PATH = "report"
+    DEFAULT_FILE_ENCODING = "utf-8"
 
     def __init__(self):
         pass
 
+    @abstractmethod
+    def set_context(self, context):
+        """
+        Set the context for the plugins.
+
+        Called from `__main` to set any context in plugins, instead of doing it later.
+        """
+
     @abstractmethod
     def get_output_path(self):
         """
@@ -85,14 +94,18 @@ class ProjectSummarizerPlugin(ABC):
         Save the specified summary file.
         """
 
-        summary_output_path = os.path.dirname(json_file_to_write)
-        if not os.path.exists(summary_output_path):
-            print(f"Summary output path '{summary_output_path}' does not exist.")
-            sys.exit(1)
+        # summary_output_path = os.path.dirname(json_file_to_write)
+        # if not os.path.exists(summary_output_path):
+        #     print(f"Summary output path '{summary_output_path}' does not exist.")
+        #     sys.exit(1)
 
         full_test_summary_output_path = os.path.abspath(json_file_to_write)
         try:
-            with open(full_test_summary_output_path, "w", encoding="utf-8") as outfile:
+            with open(
+                full_test_summary_output_path,
+                "w",
+                encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING,
+            ) as outfile:
                 json.dump(object_to_write.to_dict(), outfile, indent=4)
                 outfile.write("\n\n")
         except IOError as ex:
@@ -101,14 +114,3 @@ class ProjectSummarizerPlugin(ABC):
                 + f"' was not written ({ex})."
             )
             sys.exit(1)
-
-    @staticmethod
-    def compute_published_path_to_file(file_to_publish):
-        """
-        Compute the path for the given file, assuming it will be placed in the publish directory.
-        """
-
-        return os.path.join(
-            ProjectSummarizerPlugin.SUMMARY_PUBLISH_PATH,
-            os.path.basename(file_to_publish),
-        )
diff --git a/project_summarizer/summarize_context.py b/project_summarizer/summarize_context.py
new file mode 100644
index 0000000..7b2597a
--- /dev/null
+++ b/project_summarizer/summarize_context.py
@@ -0,0 +1,38 @@
+"""
+Module to hold the context to be passed to the summarizers.
+"""
+import os
+
+
+class SummarizeContext:
+    """
+    Class to hold the context to be passed to the summarizers.
+    """
+
+    def __init__(self, report_dir, publish_dir):
+        self.__report_dir = report_dir
+        self.__publish_dir = publish_dir
+
+    @property
+    def report_dir(self):
+        """
+        Directory used to create report summaries in.
+        """
+        return self.__report_dir
+
+    @property
+    def publish_dir(self):
+        """
+        Directory used to publish summaries to.
+        """
+        return self.__publish_dir
+
+    def compute_published_path_to_file(self, file_to_publish):
+        """
+        Compute the path for the given file, assuming it will be placed in the publish directory.
+        """
+
+        return os.path.join(
+            self.__publish_dir,
+            os.path.basename(file_to_publish),
+        )
diff --git a/publish/coverage.json b/publish/coverage.json
index 7333265..60b391f 100644
--- a/publish/coverage.json
+++ b/publish/coverage.json
@@ -2,12 +2,12 @@
     "projectName": "project_summarizer",
     "reportSource": "Coverage.py",
     "branchLevel": {
-        "totalMeasured": 192,
-        "totalCovered": 192
+        "totalMeasured": 198,
+        "totalCovered": 198
     },
     "lineLevel": {
-        "totalMeasured": 596,
-        "totalCovered": 596
+        "totalMeasured": 631,
+        "totalCovered": 631
     }
 }
 
diff --git a/publish/pylint_suppression.json b/publish/pylint_suppression.json
index 0222aba..f39eeb0 100644
--- a/publish/pylint_suppression.json
+++ b/publish/pylint_suppression.json
@@ -17,6 +17,7 @@
             "too-few-public-methods": 1
         },
         "project_summarizer/project_summarizer_plugin.py": {},
+        "project_summarizer/summarize_context.py": {},
         "project_summarizer/test_results_model.py": {
             "too-many-arguments": 1
         },
diff --git a/setup.py b/setup.py
index d719b12..86f6169 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,8 @@ from setuptools import setup
 
 
 def __parse_requirements():
-    lineiter = (line.strip() for line in open("install-requirements.txt", "r"))
+    with open("install-requirements.txt", "r", encoding="utf-8") as requirement_file:
+        lineiter = [line.strip() for line in requirement_file]
     return [line for line in lineiter if line and not line.startswith("#")]
 
 
@@ -55,7 +56,7 @@ PACKAGE_MODULES = [
 setup(
     name=PACKAGE_NAME,
     version=SEMANTIC_VERSION,
-    python_requires=">=" + MINIMUM_PYTHON_VERSION,
+    python_requires=f">={MINIMUM_PYTHON_VERSION}",
     install_requires=__parse_requirements(),
     author=AUTHOR,
     author_email=AUTHOR_EMAIL,


Test patch:
diff --git a/publish/test-results.json b/publish/test-results.json
index 04482f3..8dc14b3 100644
--- a/publish/test-results.json
+++ b/publish/test-results.json
@@ -28,7 +28,7 @@
         },
         {
             "name": "test.test_main",
-            "totalTests": 8,
+            "totalTests": 10,
             "failedTests": 0,
             "errorTests": 0,
             "skippedTests": 0,
@@ -36,7 +36,7 @@
         },
         {
             "name": "test.test_publish_scenarios",
-            "totalTests": 9,
+            "totalTests": 10,
             "failedTests": 0,
             "errorTests": 0,
             "skippedTests": 0,
diff --git a/test/pytest_execute.py b/test/pytest_execute.py
index 43f4366..feda72c 100644
--- a/test/pytest_execute.py
+++ b/test/pytest_execute.py
@@ -9,6 +9,8 @@ import sys
 import traceback
 from abc import ABC, abstractmethod
 
+from project_summarizer.project_summarizer_plugin import ProjectSummarizerPlugin
+
 LOGGER = logging.getLogger(__name__)
 
 
@@ -194,7 +196,9 @@ class InProcessResult:
         """
 
         split_expected_contents = expected_contents.split("\n")
-        with open(file_path, "r", encoding="utf-8") as infile:
+        with open(
+            file_path, "r", encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING
+        ) as infile:
             split_actual_contents = infile.readlines()
         for line_index, line_content in enumerate(split_actual_contents):
             if line_content[-1] == "\n":
diff --git a/test/test_coverage_scenarios.py b/test/test_coverage_scenarios.py
index 9ea461a..8336b18 100644
--- a/test/test_coverage_scenarios.py
+++ b/test/test_coverage_scenarios.py
@@ -10,7 +10,6 @@ from test.test_scenarios import (
     ONLY_CHANGES_COMMAND_LINE_FLAG,
     PUBLISH_COMMAND_LINE_FLAG,
     PUBLISH_DIRECTORY,
-    REPORT_DIRECTORY,
     MainlineExecutor,
     get_coverage_file_name,
     setup_directories,
@@ -320,7 +319,8 @@ def test_summarize_simple_cobertura_report_and_publish(
     suppplied_arguments = [PUBLISH_COMMAND_LINE_FLAG]
 
     expected_output = (
-        f"Publish directory '{PUBLISH_DIRECTORY}' does not exist.  Creating."
+        f"Publish directory '{PUBLISH_DIRECTORY}' does not exist.  Creating.\n"
+        + f"Published: {os.path.join(PUBLISH_DIRECTORY, COVERAGE_SUMMARY_FILE_NAME)}"
     )
     expected_error = ""
     expected_return_code = 0
@@ -531,9 +531,13 @@ def test_summarize_bad_report_directory():
 
     suppplied_arguments = [COBERTURA_COMMAND_LINE_FLAG, cobertura_coverage_file]
 
-    expected_output = f"Summary output path '{REPORT_DIRECTORY}' does not exist."
-    expected_error = ""
-    expected_return_code = 1
+    expected_output = ""
+    expected_error = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
+               [--only-changes] [--publish] [--quiet]
+               [--columns DISPLAY_COLUMNS]
+main.py: error: argument --report-dir: Path 'report' does not exist."""
+    expected_return_code = 2
 
     # Act
     execute_results = executor.invoke_main(
diff --git a/test/test_main.py b/test/test_main.py
index 3841a6c..d5b8cb8 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -1,8 +1,8 @@
 """
 Module to provide tests related to the basic parts of the scanner.
 """
-
-from test.test_scenarios import MainlineExecutor
+import os
+from test.test_scenarios import MainlineExecutor, setup_directories
 
 
 def test_scanner_with_no_parameters():
@@ -12,29 +12,43 @@ def test_scanner_with_no_parameters():
 
     # Arrange
     scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = []
 
     expected_return_code = 2
-    expected_output = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_output = """Error: Either --publish or one of the reporting arguments mush be specified.
+usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
+                        Specifies the number of character columns to use in
+                        the console summary."""
     expected_error = ""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -50,29 +64,43 @@ def test_scanner_with_no_parameters_through_module():
 
     # Arrange
     scanner = MainlineExecutor(use_module=True)
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = []
 
     expected_return_code = 2
-    expected_output = """usage: __main.py__ [-h] [--version] [--cobertura path] [--junit path]
-                   [--only-changes] [--publish] [--quiet]
+    expected_error = ""
+    expected_output = """Error: Either --publish or one of the reporting arguments mush be specified.
+usage: __main.py__ [-h] [--version] [--report-dir REPORT_DIR]
+                   [--publish-dir PUBLISH_DIR] [--cobertura path]
+                   [--junit path] [--only-changes] [--publish] [--quiet]
                    [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
-    expected_error = ""
+                        Specifies the number of character columns to use in
+                        the console summary."""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -88,29 +116,43 @@ def test_scanner_with_no_parameters_through_main():
 
     # Arrange
     scanner = MainlineExecutor(use_main=True)
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = []
 
     expected_return_code = 2
-    expected_output = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_output = """Error: Either --publish or one of the reporting arguments mush be specified.
+usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
+                        Specifies the number of character columns to use in
+                        the console summary."""
     expected_error = ""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -125,29 +167,42 @@ def test_scanner_with_dash_h():
 
     # Arrange
     scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = ["-h"]
 
     expected_return_code = 0
-    expected_output = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_output = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
+                        Specifies the number of character columns to use in
+                        the console summary."""
     expected_error = ""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -162,29 +217,43 @@ def test_scanner_with_dash_dash_quiet():
 
     # Arrange
     scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = ["--quiet"]
 
     expected_return_code = 2
-    expected_output = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_output = """Error: Either --publish or one of the reporting arguments mush be specified.
+usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
+                        Specifies the number of character columns to use in
+                        the console summary."""
     expected_error = ""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -199,17 +268,21 @@ def test_scanner_with_dash_dash_columns():
 
     # Arrange
     scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = ["--columns"]
 
     expected_return_code = 2
     expected_output = ""
-    expected_error = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_error = """ usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 main.py: error: argument --columns: expected one argument"""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -224,29 +297,43 @@ def test_scanner_with_dash_dash_columns_good_number():
 
     # Arrange
     scanner = MainlineExecutor()
-    supplied_arguments = ["--columns", "100"]
+    temporary_work_directory, _, _ = setup_directories()
 
+    supplied_arguments = ["--columns", "100"]
     expected_return_code = 2
-    expected_output = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_output = """Error: Either --publish or one of the reporting arguments mush be specified.
+usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
 
 Summarize Python files.
 
 optional arguments:
-  -h, --help            show this help message and exit
-  --version             show program's version number and exit
-  --cobertura path      source file name for cobertura test coverage reporting
-  --junit path          source file name for junit test result reporting
-  --only-changes        only_changes
-  --publish             publish
-  --quiet               quiet_mode
+  -h, --help            Show this help message and exit.
+  --version             Show program's version number and exit.
+  --report-dir REPORT_DIR
+                        Directory to generate the summary reports in.
+  --publish-dir PUBLISH_DIR
+                        Directory to publish the summary reports to.
+  --cobertura path      Source file name for cobertura test coverage
+                        reporting.
+  --junit path          Source file name for junit test result reporting.
+  --only-changes        Only the summary items that have changed are displayed
+                        in the console summary.
+  --publish             Publish the summaries to the publish directory and
+                        exit.
+  --quiet               The report summary files will be generated, but no
+                        summary will be output to the console.
   --columns DISPLAY_COLUMNS
-                        display_columns"""
+                        Specifies the number of character columns to use in
+                        the console summary."""
     expected_error = ""
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
@@ -261,17 +348,87 @@ def test_scanner_with_dash_dash_columns_bad_number():
 
     # Arrange
     scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
     supplied_arguments = ["--columns", "20"]
 
     expected_return_code = 2
     expected_output = ""
-    expected_error = """usage: main.py [-h] [--version] [--cobertura path] [--junit path]
+    expected_error = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
+               [--only-changes] [--publish] [--quiet]
+               [--columns DISPLAY_COLUMNS]
+main.py: error: argument --columns: Value '20' is not an integer between between 50 and 200."""
+
+    # Act
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
+
+    # Assert
+    execute_results.assert_results(
+        expected_output, expected_error, expected_return_code
+    )
+
+
+def test_scanner_with_dash_dash_report_dir_with_non_existant():
+    """
+    Test to make sure we get help if '--report-dir' is supplied with a non-existant directory.
+    """
+
+    # Arrange
+    scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
+    supplied_arguments = ["--report-dir", "alternate-reports"]
+
+    assert not os.path.exists("alternate-reports")
+
+    expected_return_code = 2
+    expected_output = ""
+    expected_error = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
+               [--only-changes] [--publish] [--quiet]
+               [--columns DISPLAY_COLUMNS]
+main.py: error: argument --report-dir: Path 'alternate-reports' does not exist.
+"""
+
+    # Act
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
+
+    # Assert
+    execute_results.assert_results(
+        expected_output, expected_error, expected_return_code
+    )
+
+
+def test_scanner_with_dash_dash_report_dir_with_non_directory():
+    """
+    Test to make sure we get help if '--report-dir' is supplied with a file instead of a directory.
+    """
+
+    # Arrange
+    scanner = MainlineExecutor()
+    temporary_work_directory, _, _ = setup_directories()
+    assert os.path.exists("README.md") and os.path.isfile("README.md")
+    readme_path = os.path.abspath("README.md")
+    supplied_arguments = ["--report-dir", readme_path]
+
+    expected_return_code = 2
+    expected_output = ""
+    expected_error = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
                [--only-changes] [--publish] [--quiet]
                [--columns DISPLAY_COLUMNS]
-main.py: error: argument --columns: invalid __verify_display_columns value: '20'"""
+main.py: error: argument --report-dir: Path '{path}' is not an existing directory.
+""".replace(
+        "{path}", readme_path
+    )
 
     # Act
-    execute_results = scanner.invoke_main(arguments=supplied_arguments)
+    execute_results = scanner.invoke_main(
+        arguments=supplied_arguments, cwd=temporary_work_directory.name
+    )
 
     # Assert
     execute_results.assert_results(
diff --git a/test/test_publish_scenarios.py b/test/test_publish_scenarios.py
index afd3ccb..3078073 100644
--- a/test/test_publish_scenarios.py
+++ b/test/test_publish_scenarios.py
@@ -24,6 +24,8 @@ from test.test_scenarios import (
     setup_directories,
 )
 
+from project_summarizer.project_summarizer_plugin import ProjectSummarizerPlugin
+
 
 def test_summarize_simple_junit_report_and_publish_with_existing_publish():
     """
@@ -40,8 +42,54 @@ def test_summarize_simple_junit_report_and_publish_with_existing_publish():
     summary_result_file = os.path.join(publish_directory, RESULTS_SUMMARY_FILE_NAME)
 
     suppplied_arguments = [PUBLISH_COMMAND_LINE_FLAG]
+    publish_path = os.path.join(
+        ProjectSummarizerPlugin.DEFAULT_SUMMARY_PUBLISH_PATH, "test-results.json"
+    )
 
-    expected_output = ""
+    expected_output = f"Published: {publish_path}"
+    expected_error = ""
+    expected_return_code = 0
+    expected_test_results_file = compose_test_results(3)
+
+    # Act
+    execute_results = executor.invoke_main(
+        arguments=suppplied_arguments, cwd=temporary_work_directory.name
+    )
+
+    # Assert
+    execute_results.assert_results(
+        expected_output, expected_error, expected_return_code
+    )
+    execute_results.assert_resultant_file(
+        summary_result_file, expected_test_results_file
+    )
+
+
+def test_summarize_simple_junit_report_and_publish_with_alternate_publish():
+    """
+    Test to make sure that publishing with a publish directory that already exists.
+    """
+
+    # Arrange
+    alternate_publish_directory = "alt-publish"
+    (
+        executor,
+        temporary_work_directory,
+        publish_directory,
+        _,
+    ) = test_summarize_simple_junit_report(
+        create_publish_directory=True,
+        alternate_publish_directory=alternate_publish_directory,
+    )
+    summary_result_file = os.path.join(publish_directory, RESULTS_SUMMARY_FILE_NAME)
+
+    suppplied_arguments = [
+        PUBLISH_COMMAND_LINE_FLAG,
+        "--publish-dir",
+        alternate_publish_directory,
+    ]
+
+    expected_output = f"Published: {os.path.join(alternate_publish_directory, RESULTS_SUMMARY_FILE_NAME)}"
     expected_error = ""
     expected_return_code = 0
     expected_test_results_file = compose_test_results(3)
@@ -76,7 +124,10 @@ def test_summarize_simple_cobertura_report_and_publish_with_existing_publish():
 
     suppplied_arguments = [PUBLISH_COMMAND_LINE_FLAG]
 
-    expected_output = ""
+    output_file = os.path.join(
+        ProjectSummarizerPlugin.DEFAULT_SUMMARY_PUBLISH_PATH, COVERAGE_SUMMARY_FILE_NAME
+    )
+    expected_output = f"Published: {output_file}"
     expected_error = ""
     expected_return_code = 0
     expected_test_coverage_file = compose_coverage_summary_file()
@@ -104,7 +155,9 @@ def test_publish_with_existing_publish_as_file():
     executor = MainlineExecutor()
     temporary_work_directory, _, publish_directory = setup_directories()
 
-    with open(publish_directory, "w", encoding="utf-8") as outfile:
+    with open(
+        publish_directory, "w", encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING
+    ) as outfile:
         outfile.write("test")
 
     suppplied_arguments = [PUBLISH_COMMAND_LINE_FLAG]
diff --git a/test/test_results_scenarios.py b/test/test_results_scenarios.py
index 7cde6a4..fdea373 100644
--- a/test/test_results_scenarios.py
+++ b/test/test_results_scenarios.py
@@ -10,12 +10,13 @@ from test.test_scenarios import (
     ONLY_CHANGES_COMMAND_LINE_FLAG,
     PUBLISH_COMMAND_LINE_FLAG,
     PUBLISH_DIRECTORY,
-    REPORT_DIRECTORY,
     RESULTS_SUMMARY_FILE_NAME,
     MainlineExecutor,
     setup_directories,
 )
 
+from project_summarizer.project_summarizer_plugin import ProjectSummarizerPlugin
+
 
 def compose_test_results(total_tests):
     """
@@ -43,7 +44,9 @@ def compose_test_results(total_tests):
 
 
 def test_summarize_simple_junit_report(
-    create_publish_directory=False, temporary_work_directory=None
+    create_publish_directory=False,
+    temporary_work_directory=None,
+    alternate_publish_directory=None,
 ):
     """
     Test the summarizing of a simple junit report with no previous summary.
@@ -53,6 +56,7 @@ def test_summarize_simple_junit_report(
     executor = MainlineExecutor()
     temporary_work_directory, report_directory, publish_directory = setup_directories(
         create_publish_directory=create_publish_directory,
+        alternate_publish_directory=alternate_publish_directory,
         temporary_work_directory=temporary_work_directory,
     )
     junit_test_file = os.path.join(
@@ -285,7 +289,8 @@ def test_summarize_simple_junit_report_and_publish(
     suppplied_arguments = [PUBLISH_COMMAND_LINE_FLAG]
 
     expected_output = (
-        f"Publish directory '{PUBLISH_DIRECTORY}' does not exist.  Creating."
+        f"Publish directory '{PUBLISH_DIRECTORY}' does not exist.  Creating.\n"
+        + f"Published: {os.path.join(PUBLISH_DIRECTORY, RESULTS_SUMMARY_FILE_NAME)}"
     )
     expected_error = ""
     expected_return_code = 0
@@ -763,9 +768,13 @@ def test_summarize_bad_report_directory():
 
     suppplied_arguments = [JUNIT_COMMAND_LINE_FLAG, junit_test_file]
 
-    expected_output = f"Summary output path '{REPORT_DIRECTORY}' does not exist."
-    expected_error = ""
-    expected_return_code = 1
+    expected_output = ""
+    expected_error = """usage: main.py [-h] [--version] [--report-dir REPORT_DIR]
+               [--publish-dir PUBLISH_DIR] [--cobertura path] [--junit path]
+               [--only-changes] [--publish] [--quiet]
+               [--columns DISPLAY_COLUMNS]
+main.py: error: argument --report-dir: Path 'report' does not exist."""
+    expected_return_code = 2
 
     # Act
     execute_results = executor.invoke_main(
@@ -797,7 +806,9 @@ def test_summarize_invalid_published_summary_file():
     )
     summary_result_file = os.path.join(publish_directory, RESULTS_SUMMARY_FILE_NAME)
 
-    with open(summary_result_file, "w", encoding="utf-8") as outfile:
+    with open(
+        summary_result_file, "w", encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING
+    ) as outfile:
         outfile.write("this is not a json file")
 
     suppplied_arguments = [JUNIT_COMMAND_LINE_FLAG, junit_test_file]
@@ -939,7 +950,9 @@ def test_sample_1():
         + '"errorTests": 0, "skippedTests": 0, "elapsedTimeInMilliseconds": 0}'
         + "]}"
     )
-    with open(summary_result_file, "w", encoding="utf-8") as outfile:
+    with open(
+        summary_result_file, "w", encoding=ProjectSummarizerPlugin.DEFAULT_FILE_ENCODING
+    ) as outfile:
         outfile.write(previous_test_summary_contents)
 
     suppplied_arguments = [JUNIT_COMMAND_LINE_FLAG, junit_test_file]
diff --git a/test/test_scenarios.py b/test/test_scenarios.py
index 958aa3b..4def754 100644
--- a/test/test_scenarios.py
+++ b/test/test_scenarios.py
@@ -8,6 +8,7 @@ from test.pytest_execute import InProcessExecution
 
 from project_summarizer.__main__ import main
 from project_summarizer.main import ProjectSummarizer
+from project_summarizer.project_summarizer_plugin import ProjectSummarizerPlugin
 
 JUNIT_COMMAND_LINE_FLAG = "--junit"
 COBERTURA_COMMAND_LINE_FLAG = "--cobertura"
@@ -15,13 +16,13 @@ COBERTURA_COMMAND_LINE_FLAG = "--cobertura"
 PUBLISH_COMMAND_LINE_FLAG = "--publish"
 ONLY_CHANGES_COMMAND_LINE_FLAG = "--only-changes"
 
-REPORT_DIRECTORY = "report"
-PUBLISH_DIRECTORY = "publish"
+REPORT_DIRECTORY = ProjectSummarizerPlugin.DEFAULT_REPORT_PUBLISH_PATH
+PUBLISH_DIRECTORY = ProjectSummarizerPlugin.DEFAULT_SUMMARY_PUBLISH_PATH
 
 JUNIT_RESULTS_FILE_NAME = "tests.xml"
 RESULTS_SUMMARY_FILE_NAME = "test-results.json"
-COVERAGE_SUMMARY_FILE_NAME = "coverage.json"
 
+COVERAGE_SUMMARY_FILE_NAME = "coverage.json"
 __COBERTURA_COVERAGE_FILE_NAME = "coverage.xml"
 __COBERTURA_NON_WINDOWS_COVERAGE_FILE_NAME = "coverage-non-windows.xml"
 
@@ -91,6 +92,7 @@ main.py 0.5.0
 def setup_directories(
     create_report_directory=True,
     create_publish_directory=False,
+    alternate_publish_directory=None,
     temporary_work_directory=None,
 ):
     """
@@ -100,12 +102,24 @@ def setup_directories(
 
     if not temporary_work_directory:
         temporary_work_directory = tempfile.TemporaryDirectory()
-    report_directory = os.path.join(temporary_work_directory.name, "report")
+
+    report_directory = os.path.join(
+        temporary_work_directory.name,
+        ProjectSummarizerPlugin.DEFAULT_REPORT_PUBLISH_PATH,
+    )
     if create_report_directory:
         os.makedirs(report_directory)
-    publish_directory = os.path.join(temporary_work_directory.name, "publish")
+
+    alternate_publish_directory = (
+        alternate_publish_directory
+        or ProjectSummarizerPlugin.DEFAULT_SUMMARY_PUBLISH_PATH
+    )
+    publish_directory = os.path.join(
+        temporary_work_directory.name, alternate_publish_directory
+    )
     if create_publish_directory:
         os.makedirs(publish_directory)
+
     return temporary_work_directory, report_directory, publish_directory