From d198f62db8ee35331960ab722a4edc908caf92f8 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:53:31 -0700 Subject: [PATCH 01/31] move exception handling to prevent error from iterating over None --- metplotpy/plots/config.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/metplotpy/plots/config.py b/metplotpy/plots/config.py index 9f1f9f7f..88d02d4c 100644 --- a/metplotpy/plots/config.py +++ b/metplotpy/plots/config.py @@ -490,6 +490,9 @@ def _get_show_legend(self) -> list: """ show_legend_settings = self.get_config_value('show_legend') + if show_legend_settings is None: + raise ValueError("ERROR: show_legend parameter is not provided.") + # Support all variations of setting the show_legend: '1', 1, "true" (any combination of cases), True (boolean) updated_show_legend_settings = [] for legend_setting in show_legend_settings: @@ -499,10 +502,6 @@ def _get_show_legend(self) -> list: else: updated_show_legend_settings.append(int(0)) - - if show_legend_settings is None: - raise ValueError("ERROR: show_legend parameter is not provided.") - return self.create_list_by_series_ordering(list(updated_show_legend_settings)) def _get_markers(self): From b8460205a3f873f640552d745b96496243a2b475 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:00:26 -0700 Subject: [PATCH 02/31] Refactor pytests: write output to test/test_output (unless METPLOTPY_TEST_OUTPUT is set) and preserve output files so they can be compared to previous iterations, consolidate redundant tests, use a module fixture to set up environment once per file --- test/.gitignore | 1 + test/bar/bar_with_nones.yaml | 4 +- test/bar/custom_bar.yaml | 8 +- test/bar/custom_defaultpoints1_bar.yaml | 137 -------------- test/bar/custom_points1_bar.yaml | 137 -------------- test/bar/test_bar.py | 147 +++------------ test/bar/threshold_bar.yaml | 4 +- test/box/custom_box.yaml | 9 +- test/box/custom_box_defaultpoints1.yaml | 2 +- test/box/custom_box_points1.yaml | 4 +- test/box/simple_box.yaml | 2 +- test/box/test_box.py | 107 +---------- test/conftest.py | 40 +++- test/contour/custom_contour.yaml | 2 +- test/contour/test_contour.py | 40 +--- .../test_difficulty_index_plotting.py | 12 +- test/eclv/custom_eclv.yaml | 2 +- test/eclv/custom_eclv_ctc.yaml | 2 +- test/eclv/custom_eclv_pct.yaml | 2 +- test/eclv/test_eclv.py | 66 ++----- test/ens_ss/custom2_ens_ss.yaml | 120 ------------ test/ens_ss/custom_ens_ss.yaml | 8 +- test/ens_ss/test_ens_ss.py | 78 ++------ .../custom_equivalence_testing_bounds.yaml | 3 +- .../custom_equivalence_testing_bounds2.yaml | 172 ------------------ .../test_equivalence_testing_bounds.py | 70 +------ test/histogram/prob_hist.yaml | 2 +- test/histogram/rank_hist.yaml | 2 +- test/histogram/rel_hist.yaml | 2 +- test/histogram/test_prob_hist.py | 26 +-- test/histogram/test_rank_hist.py | 28 +-- test/histogram/test_rel_hist.py | 30 +-- test/histogram_2d/custom_histogram_2d.yaml | 2 +- test/histogram_2d/minimal_histogram_2d.yaml | 2 +- test/histogram_2d/test_histogram_2d.py | 36 ++-- test/import/test_import.py | 3 - 36 files changed, 166 insertions(+), 1146 deletions(-) create mode 100644 test/.gitignore delete mode 100644 test/bar/custom_defaultpoints1_bar.yaml delete mode 100644 test/bar/custom_points1_bar.yaml delete mode 100644 test/ens_ss/custom2_ens_ss.yaml delete mode 100644 test/equivalence_testing_bounds/custom_equivalence_testing_bounds2.yaml diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..46c8ed0d --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +test_output/ diff --git a/test/bar/bar_with_nones.yaml b/test/bar/bar_with_nones.yaml index a120bf44..f5714e53 100644 --- a/test/bar/bar_with_nones.yaml +++ b/test/bar/bar_with_nones.yaml @@ -163,8 +163,8 @@ ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 -stat_input: ./bar_with_nones.data -plot_filename: ./bar_with_nones.png +stat_input: !ENV '${TEST_DIR}/bar_with_nones.data' +plot_filename: !ENV '${TEST_OUTPUT}/bar_with_nones.png' show_legend: -True \ No newline at end of file diff --git a/test/bar/custom_bar.yaml b/test/bar/custom_bar.yaml index 3361c2c7..eb9df890 100644 --- a/test/bar/custom_bar.yaml +++ b/test/bar/custom_bar.yaml @@ -66,11 +66,7 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -#points_path: /dir_to_save_points1_file +points_path: !ENV '${TEST_OUTPUT}/intermed_files' random_seed: null @@ -136,7 +132,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/bar.data' -plot_filename: !ENV '${TEST_DIR}/bar.png' +plot_filename: !ENV '${TEST_OUTPUT}/bar.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/bar/custom_defaultpoints1_bar.yaml b/test/bar/custom_defaultpoints1_bar.yaml deleted file mode 100644 index ba734352..00000000 --- a/test/bar/custom_defaultpoints1_bar.yaml +++ /dev/null @@ -1,137 +0,0 @@ -alpha: 0.05 -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -circular_block_bootstrap: 'True' -colors: -- '#ff0000' -- '#8000ff' -con_series: -- 1 -- 1 -create_html: 'False' -derived_series_1: [] -dump_points_1: 'True' -# allow points1 file to be saved to the default location -#points_path: './intermed_files' -eqbound_high: 0.001 -eqbound_low: -0.001 -event_equal: 'False' -fcst_var_val_1: - TMP: - - ME -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: -- '12' -- '24' -indy_vals: -- '120000' -- '240000' -indy_var: fcst_lead -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: None -list_stat_1: -- ME -mar: -- 8 -- 4 -- 5 -- 4 -method: bca -mgp: -- 1 -- 1 -- 0 -num_iterations: 1 -num_threads: -1 -plot_caption: '' -plot_disp: -- 'True' -- 'True' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -random_seed: null - -series_order: -- 1 -- 2 - - -series_val_1: - model: - - AFWAOCv3.5.1_d01 - - NoahMPv3.5.1_d01 -series_val_2: {} -show_nstats: 'True' - -sync_yaxes: 'False' -title: test title -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] -vert_plot: 'False' -x2lab_align: 0.5 -x2lab_offset: -0.5 -x2lab_size: 0.8 -x2lab_weight: 1 -x2tlab_horiz: 0.5 -x2tlab_orient: 1 -x2tlab_perp: 1 -x2tlab_size: 0.8 -xaxis: test x_label -xaxis_reverse: 'True' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -y2lab_align: 0.5 -y2lab_offset: 1 -y2lab_size: 1 -y2lab_weight: 1 -y2lim: [] -y2tlab_horiz: 0.5 -y2tlab_orient: 1 -y2tlab_perp: 1 -y2tlab_size: 1.0 -yaxis_1: test y_label -yaxis_2: '' -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 - -stat_input: !ENV '${TEST_DIR}/bar.data' -plot_filename: !ENV '${TEST_DIR}/bar_defaultpoints1.png' -show_legend: - -True - -True \ No newline at end of file diff --git a/test/bar/custom_points1_bar.yaml b/test/bar/custom_points1_bar.yaml deleted file mode 100644 index bfc23987..00000000 --- a/test/bar/custom_points1_bar.yaml +++ /dev/null @@ -1,137 +0,0 @@ -alpha: 0.05 -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -circular_block_bootstrap: 'True' -colors: -- '#ff0000' -- '#8000ff' -con_series: -- 1 -- 1 -create_html: 'False' -derived_series_1: [] -dump_points_1: 'True' -points_path: !ENV '${TEST_DIR}/intermed_files' -eqbound_high: 0.001 -eqbound_low: -0.001 -event_equal: 'False' -fcst_var_val_1: - TMP: - - ME -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: -- '12' -- '24' -indy_vals: -- '120000' -- '240000' -indy_var: fcst_lead -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: None -list_stat_1: -- ME -mar: -- 8 -- 4 -- 5 -- 4 -method: bca -mgp: -- 1 -- 1 -- 0 -num_iterations: 1 -num_threads: -1 -plot_caption: '' -plot_disp: -- 'True' -- 'True' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -random_seed: null - -series_order: -- 1 -- 2 - - -series_val_1: - model: - - AFWAOCv3.5.1_d01 - - NoahMPv3.5.1_d01 -series_val_2: {} -show_nstats: 'True' - -sync_yaxes: 'False' -title: test title -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] -vert_plot: 'False' -x2lab_align: 0.5 -x2lab_offset: -0.5 -x2lab_size: 0.8 -x2lab_weight: 1 -x2tlab_horiz: 0.5 -x2tlab_orient: 1 -x2tlab_perp: 1 -x2tlab_size: 0.8 -xaxis: test x_label -xaxis_reverse: 'True' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -y2lab_align: 0.5 -y2lab_offset: 1 -y2lab_size: 1 -y2lab_weight: 1 -y2lim: [] -y2tlab_horiz: 0.5 -y2tlab_orient: 1 -y2tlab_perp: 1 -y2tlab_size: 1.0 -yaxis_1: test y_label -yaxis_2: '' -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 - -stat_input: !ENV '${TEST_DIR}/bar.data' -plot_filename: !ENV '${TEST_DIR}/bar_points1.png' - -show_legend: - -True - -True diff --git a/test/bar/test_bar.py b/test/bar/test_bar.py index eb0c5fee..9f85cb1f 100644 --- a/test/bar/test_bar.py +++ b/test/bar/test_bar.py @@ -3,159 +3,60 @@ import pytest from metplotpy.plots.bar import bar -# from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['bar.png', 'bar.points1'] - @pytest.fixture -def setup(remove_files, setup_env): +def setup(remove_files, module_setup_env): # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_bar.yaml" + remove_files(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files'), ['bar.points1']) + remove_files(os.environ['TEST_OUTPUT'], ['bar.png']) - # Invoke the command to generate a Bar plot based on - # the custom_bar.yaml custom config file. + custom_config_filename = f"{cwd}/custom_bar.yaml" bar.main(custom_config_filename) @pytest.fixture -def setup_nones(remove_files, setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) +def setup_nones(remove_files, module_setup_env): + # Cleanup the plotfile from any previous run + remove_files(os.environ['TEST_OUTPUT'], ['bar_with_nones.png']) custom_config_filename = f"{cwd}/bar_with_nones.yaml" - # Invoke the command to generate a Bar plot based on - # the custom_bar.yaml custom config file. bar.main(custom_config_filename) -def test_files_exist(setup, remove_files): - """ - Checking that the plot and data files are getting created - """ - check_files = ('bar.png', 'bar.points1') - for test_input in check_files: - print(f'Checking if {cwd}/{test_input} is found') - assert os.path.isfile(f"{cwd}/{test_input}") - remove_files(cwd, check_files) - - -def test_no_nans_in_points_file(setup, remove_files): - """ - Checking that the points1 intermediate file does not - have any NaN's. This is indicative of a problem with the _create_series_points() method. - """ +def test_custom_bar(setup, remove_files): + """Checking that the plot and data files are getting created and + that there are no NaNs in the data file. Check for NaNs in a file that + is known to have NaNs to ensure check works as expected.""" + check_files = ("bar.png", "intermed_files/bar.points1") + for check_file in check_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{check_file}") - # Check for NaN's in the intermediate files, line.points1 and line.points2 - # Fail if there are any NaN's-this indicates something went wrong with the - # line_series.py module's _create_series_points() method. - nans_found = False - with open(f"{cwd}/bar.points1", "r") as f: + with open(f"{os.environ['TEST_OUTPUT']}/intermed_files/bar.points1", "r") as f: data = f.read() - if "NaN" in data: - nans_found = True - - assert not nans_found + assert "NaN" not in data # Verify that the nan.points1 file does indeed trigger a "nans_found" with open(f"{cwd}/nan.points1", "r") as f: data = f.read() - if "NaN" in data: - nans_found = True - - assert nans_found - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("fails on linux host machines") -def test_images_match(setup, remove_files): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/bar_expected.png', f'{cwd}/bar.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) + assert "NaN" in data -@pytest.mark.skip("fails on linux host machines") -def test_none_data_images_match(setup_nones): +def test_bar_with_nones(setup_nones): """ Compare an expected plot with the newly created plot to verify that the plot hasn't changed in appearance. """ - comparison = CompareImages(f'{cwd}/expected_with_nones.png', f'{cwd}/bar_with_nones.png') - assert comparison.mssim == 1 - - try: - plot_file = f'{cwd}/bar_with_nones.png' - os.remove(plot_file) - except OSError: - pass - - -def test_point_and_plot_files_exist(setup_env, remove_files): - """ - Checking that the plot and (specified location) intermediate file are getting created - """ - check_files = ("bar_points1.png", "intermed_files/bar.points1") - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_points1_bar.yaml" - intermed_dir = os.path.join(cwd, 'intermed_files') - try: - os.mkdir(intermed_dir) - except FileExistsError: - pass - - # Invoke the command to generate a Bar plot based on - # the custom_bar.yaml custom config file. - bar.main(custom_config_filename) - - for test_input in check_files: - assert os.path.isfile(f"{cwd}/{test_input}") - remove_files(cwd, check_files) - - -def test_point_and_plot_files_exist_default(setup_env, remove_files): - """ - Checking that the plot and (specified location) intermediate file are getting created - """ - check_files = ("bar_defaultpoints1.png", "bar.points1") - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_defaultpoints1_bar.yaml" + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/bar_with_nones.png") - # Invoke the command to generate a Bar plot based on - # the custom_bar.yaml custom config file. - bar.main(custom_config_filename) - - for test_input in check_files: - assert os.path.isfile(f"{cwd}/{test_input}") - - # remove the .png and .points files - remove_files(cwd, check_files) - - -@pytest.mark.skip("fails on linux host machines") -def test_threshold_plotting(setup_env, remove_files): - """ - Verify that the bar plot using data with thresholds is correct. - """ +def test_threshold_plotting(module_setup_env, remove_files): + """Verify that the bar plot using data with thresholds is correct.""" # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/threshold_bar.yaml" + remove_files(os.environ['TEST_OUTPUT'], ['threshold_bar.png']) - # Invoke the command to generate a Bar plot based on - # the custom_bar.yaml custom config file. + custom_config_filename = f"{cwd}/threshold_bar.yaml" bar.main(custom_config_filename) - - comparison = CompareImages(f'{cwd}/expected_threshold.png', f'{cwd}/threshold_bar.png') - assert comparison.mssim == 1 - remove_files(cwd, ['threshold_bar.png']) + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/threshold_bar.png") diff --git a/test/bar/threshold_bar.yaml b/test/bar/threshold_bar.yaml index 36efd591..561d7ccd 100644 --- a/test/bar/threshold_bar.yaml +++ b/test/bar/threshold_bar.yaml @@ -84,7 +84,7 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/threshold_bar.png' +plot_filename: !ENV '${TEST_OUTPUT}/threshold_bar.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -165,3 +165,5 @@ ytlab_horiz: 0.65 ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 3 +show_legend: + -True diff --git a/test/box/custom_box.yaml b/test/box/custom_box.yaml index bd2a1b61..a3749b9b 100644 --- a/test/box/custom_box.yaml +++ b/test/box/custom_box.yaml @@ -102,12 +102,7 @@ plot_type: png16m plot_units: in plot_width: 11.0 - -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -#points_path: /dir_to_save_points1_file +points_path: !ENV '${TEST_OUTPUT}/intermed_files' random_seed: null @@ -181,7 +176,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/box.data' -plot_filename: !ENV '${TEST_DIR}/box.png' +plot_filename: !ENV '${TEST_OUTPUT}/box.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/box/custom_box_defaultpoints1.yaml b/test/box/custom_box_defaultpoints1.yaml index 59720457..07e02eb7 100644 --- a/test/box/custom_box_defaultpoints1.yaml +++ b/test/box/custom_box_defaultpoints1.yaml @@ -175,7 +175,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/box.data' -plot_filename: !ENV '${TEST_DIR}/box_defaultpoints1.png' +plot_filename: !ENV '${TEST_OUTPUT}/box_defaultpoints1.png' show_legend: -True -True diff --git a/test/box/custom_box_points1.yaml b/test/box/custom_box_points1.yaml index c6be626e..4de9ece4 100644 --- a/test/box/custom_box_points1.yaml +++ b/test/box/custom_box_points1.yaml @@ -101,7 +101,7 @@ plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' random_seed: null @@ -174,7 +174,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/box.data' -plot_filename: !ENV '${TEST_DIR}/box_points1.png' +plot_filename: !ENV '${TEST_OUTPUT}/box_points1.png' show_legend: -True -True diff --git a/test/box/simple_box.yaml b/test/box/simple_box.yaml index c1fc33aa..8dbb9d7e 100644 --- a/test/box/simple_box.yaml +++ b/test/box/simple_box.yaml @@ -113,7 +113,7 @@ xaxis: FCST_LEAD xaxis_reverse: 'False' stat_input: !ENV '${TEST_DIR}/box.data' -plot_filename: !ENV '${TEST_DIR}/default_box.png' +plot_filename: !ENV '${TEST_OUTPUT}/default_box.png' show_legend: -True diff --git a/test/box/test_box.py b/test/box/test_box.py index 25ab21c4..c749a253 100644 --- a/test/box/test_box.py +++ b/test/box/test_box.py @@ -7,110 +7,19 @@ CLEANUP_FILES = ['box.png', 'box.points1'] @pytest.fixture -def setup(remove_files, setup_env): +def setup(remove_files, module_setup_env): # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_box.yaml" + remove_files(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files'), ['box.points1']) + remove_files(os.environ['TEST_OUTPUT'], ['box.png']) - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. + custom_config_filename = f"{cwd}/custom_box.yaml" box.main(custom_config_filename) -@pytest.mark.parametrize("test_input, expected", - (["box.png", True], ["box.points1", True])) -def test_files_exist(setup, test_input, expected, remove_files): +def test_custom_box(setup, remove_files): """ Checking that the plot and data files are getting created """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/box_expected.png', f'{cwd}/box.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input, expected", - (["box_expected.png", True], ["box_points1.png", True], ["intermed_files/box.points1", True])) -def test_points1_file_exist(setup_env, test_input, expected, remove_files): - """ - Checking that the plot is created and points1 output files is created - where specified in the custom_box_points1.yaml file - """ - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_box_points1.yaml" - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a box plot based on - # the custom_box_points1.yaml custom config file. - box.main(custom_config_filename) - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, ['box_points1.png']) - - -@pytest.mark.parametrize("test_input, expected", - (["box_defaultpoints1.png", True], ["box.points1", True])) -def test_defaultpoints1_file_exist(setup_env, test_input, expected, remove_files): - """ - Checking that the plot is created and points1 output files is created - in the default location (i.e. the current working dir, where the box.data file resides) - """ - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_box_defaultpoints1.yaml" - - # Invoke the command to generate a box plot based on - # the custom_box_defaultpoints1.yaml custom config file. - box.main(custom_config_filename) - assert os.path.isfile(f"{cwd}/{test_input}") == expected - - # remove the created plot and intermediate .points1 file - remove_files(cwd, ['box_defaultpoints1.png', 'box.points1']) - - -def test_no_nans_in_points_file(setup, remove_files): - """ - Checking that the points1 file does not contain NaN's - """ - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - custom_config_filename = f"{cwd}/custom_box_defaultpoints1.yaml" - - # Invoke the command to generate a box plot based on - # the custom_box_defaultpoints1.yaml custom config file. - box.main(custom_config_filename) - - # Check for NaN's in the intermediate files, line.points1 and line.points2 - # Fail if there are any NaN's-this indicates something went wrong with the - # line_series.py module's _create_series_points() method. - nans_found = False - with open(f"{cwd}/box.points1", "r") as f: - data = f.read() - if "NaN" in data: - nans_found = True - - assert not nans_found - remove_files(cwd, CLEANUP_FILES) - - # Verify that the nan.points1 file does indeed trigger a "nans_found" - with open(f"{cwd}/nan.points1", "r") as f: - data = f.read() - if "NaN" in data: - nans_found = True - - # assert - assert nans_found - - # remove the created plot and intermediate .points1 file - remove_files(cwd, ['box_defaultpoints1.png', 'box.points1']) + check_files = ("box.png", "intermed_files/box.points1") + for check_file in check_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{check_file}") diff --git a/test/conftest.py b/test/conftest.py index 264258c2..f3a72bdb 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,11 +1,20 @@ import pytest import os from unittest.mock import patch +import sys +from pathlib import Path import shutil import json import xarray as xr from pandas import DatetimeIndex +# add METplotpy directory to path so the package can be found +metplotpy_dir = str(Path(__file__).parents[1]) +sys.path.insert(0, os.path.abspath(metplotpy_dir)) + +# set METPLOTPY_BASE to the root of the repo as well +os.environ['METPLOTPY_BASE'] = metplotpy_dir + # This fixture temporarily sets the working directory # to the dir containing the test file. This means # realative file locations can be used for each test @@ -77,16 +86,45 @@ def compare_json(fig, expected_json_file): def setup_env(): def set_environ(test_dir): print("Setting up environment") - os.environ['METPLOTPY_BASE'] = f"{test_dir}/../../" os.environ['TEST_DIR'] = test_dir + # write test output under METPLOTPY_TEST_OUTPUT if set, + # otherwise write to test/test_output/ + output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', + os.path.join(test_dir, os.pardir, 'test_output')) + # write to a subdirectory named after the plot type + os.environ['TEST_OUTPUT'] = os.path.join(output_dir, os.path.basename(test_dir)) + return set_environ +@pytest.fixture(scope="module") +def module_setup_env(request): + """Module-scoped fixture that sets up environment variables once per test module. + + This fixture automatically determines the test directory from the requesting + test module's location. + """ + test_dir = request.fspath.dirname + print("Setting up environment") + os.environ['TEST_DIR'] = test_dir + # write test output under METPLOTPY_TEST_OUTPUT if set, + # otherwise write to test/test_output/ + output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', + os.path.join(test_dir, os.pardir, 'test_output')) + # write to a subdirectory named after the plot type + os.environ['TEST_OUTPUT'] = os.path.join(output_dir, os.path.basename(test_dir)) + os.makedirs(os.environ['TEST_OUTPUT'], exist_ok=True) + yield + # Optional: cleanup after all tests in the module complete + + @pytest.fixture() def remove_files(): def remove_the_files(test_dir, file_list): print("Removing the files") # loop over list of files under test_dir and remove them + if isinstance(file_list, str): + file_list = [file_list] for file in file_list: try: os.remove(os.path.join(test_dir, file)) diff --git a/test/contour/custom_contour.yaml b/test/contour/custom_contour.yaml index b53e5a71..d33ccf19 100644 --- a/test/contour/custom_contour.yaml +++ b/test/contour/custom_contour.yaml @@ -151,7 +151,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/contour.data' -plot_filename: !ENV '${TEST_DIR}/contour.png' +plot_filename: !ENV '${TEST_OUTPUT}/contour.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/contour/test_contour.py b/test/contour/test_contour.py index 7aff0c56..80c83ce3 100644 --- a/test/contour/test_contour.py +++ b/test/contour/test_contour.py @@ -1,54 +1,26 @@ import pytest import os from metplotpy.plots.contour import contour -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) @pytest.fixture -def setup(): +def setup(module_setup_env): # Cleanup the plotfile output file from any previous run cleanup() - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - - # Invoke the command to generate a contour plot based on - # the config yaml files. contour.main(f"{cwd}/custom_contour.yaml") - def cleanup(): # remove the previously created files try: plot_file = 'contour.png' - os.remove(os.path.join(cwd, plot_file)) - except OSError as e: - # Typically, when files have already been removed or - # don't exist. Ignore. + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) + except OSError: pass -@pytest.mark.parametrize("test_input, expected", - ([f"{cwd}/contour_expected.png", True], [f"{cwd}/contour.png", True] - )) -def test_files_exist(setup, test_input, expected): - """ - Checking that the plot files are getting created - """ - assert os.path.isfile(test_input) == expected - cleanup() - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup): - """ - Compare an expected plots with the - newly created plots to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/contour_expected.png', f'{cwd}/contour.png') - assert comparison.mssim == 1 - cleanup() +def test_files_exist(setup): + """Checking that the plot files are getting created""" + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'contour.png')) diff --git a/test/difficulty_index/test_difficulty_index_plotting.py b/test/difficulty_index/test_difficulty_index_plotting.py index 5dfe50c8..d92bd798 100755 --- a/test/difficulty_index/test_difficulty_index_plotting.py +++ b/test/difficulty_index/test_difficulty_index_plotting.py @@ -1,23 +1,23 @@ import os +import math + from . import example_difficulty_index as edi cwd = os.path.dirname(__file__) def test_difficulty_index_plot(): - """ - Compare difficulty index values to ensure correctness. - """ + """Compare difficulty index values to ensure correctness.""" file1 = f'{cwd}/swh_North_Pacific_5dy_ensemble.npz' lats, lons, fieldijn = edi.load_data(file1) muij, sigmaij = edi.compute_stats(fieldijn) - assert 6.988188171386719 == muij[0][10] + assert math.isclose(6.988188171386719, muij[0][10]) - assert 6.3287403106689455 == muij[18][65] + assert math.isclose(6.3287403106689455, muij[18][65]) - assert 9.475065612792969 == muij[25][100] + assert math.isclose(9.475065612792969, muij[25][100]) if __name__ == "__main__": diff --git a/test/eclv/custom_eclv.yaml b/test/eclv/custom_eclv.yaml index 18ddd739..c79150f3 100644 --- a/test/eclv/custom_eclv.yaml +++ b/test/eclv/custom_eclv.yaml @@ -113,7 +113,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/eclv.data' -plot_filename: !ENV '${TEST_DIR}/eclv.png' +plot_filename: !ENV '${TEST_OUTPUT}/eclv.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/eclv/custom_eclv_ctc.yaml b/test/eclv/custom_eclv_ctc.yaml index 80d7c731..dfae7fea 100644 --- a/test/eclv/custom_eclv_ctc.yaml +++ b/test/eclv/custom_eclv_ctc.yaml @@ -112,7 +112,7 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/eclv_ctc.data' -plot_filename: !ENV '${TEST_DIR}/eclv_ctc.png' +plot_filename: !ENV '${TEST_OUTPUT}/eclv_ctc.png' show_legend: -True diff --git a/test/eclv/custom_eclv_pct.yaml b/test/eclv/custom_eclv_pct.yaml index bc6e3a4a..bb8a3dc6 100644 --- a/test/eclv/custom_eclv_pct.yaml +++ b/test/eclv/custom_eclv_pct.yaml @@ -103,6 +103,6 @@ ytlab_perp: 0.5 ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/eclv_pct.data' -plot_filename: !ENV '${TEST_DIR}/eclv_pct.png' +plot_filename: !ENV '${TEST_OUTPUT}/eclv_pct.png' show_legend: -True \ No newline at end of file diff --git a/test/eclv/test_eclv.py b/test/eclv/test_eclv.py index 37bf24b6..aca4467b 100644 --- a/test/eclv/test_eclv.py +++ b/test/eclv/test_eclv.py @@ -1,67 +1,29 @@ import pytest import os from metplotpy.plots.eclv import eclv -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -@pytest.fixture -def setup(): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - # Invoke the command to generate a ECLV plots based on - # the config yaml files. - - eclv.main(f"{cwd}/custom_eclv_pct.yaml") - eclv.main(f"{cwd}/custom_eclv.yaml") - eclv.main(f"{cwd}/custom_eclv_ctc.yaml") - - -def cleanup(): +def cleanup(plot_file): # remove the previously created files try: - plot_file = 'eclv_pct.png' - os.remove(os.path.join(cwd, plot_file)) - plot_file = 'eclv.png' - os.remove(os.path.join(cwd, plot_file)) - plot_file = 'eclv_ctc.png' - os.remove(os.path.join(cwd, plot_file)) - - except OSError as e: - # Typically, when files have already been removed or - # don't exist. Ignore. + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) + except OSError: pass -@pytest.mark.parametrize("test_input, expected", - ([f"{cwd}/eclv_pct_expected.png", True], [f"{cwd}/eclv_pct.png", True], - [f"{cwd}/eclv_ctc_expected.png", True], [f"{cwd}/eclv_ctc.png", True], - [f"{cwd}/eclv_expected.png", True], [f"{cwd}/eclv.png", True])) -def test_files_exist(setup, test_input, expected): +@pytest.mark.parametrize( + "yaml_file, expected_output", [ + ("custom_eclv.yaml", "eclv.png"), + ("custom_eclv_pct.yaml", "eclv_pct.png"), + ("custom_eclv_ctc.yaml", "eclv_ctc.png"), + ] +) +def test_files_exist(module_setup_env, yaml_file, expected_output): """ Checking that the plot files are getting created """ - assert os.path.isfile(test_input) == expected - cleanup() - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup): - """ - Compare an expected plots with the - newly created plots to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/eclv_pct_expected.png', './eclv_pct.png') - assert comparison.mssim == 1 - - comparison = CompareImages(f'{cwd}/eclv_expected.png', './eclv.png') - assert comparison.mssim == 1 - - comparison = CompareImages(f'{cwd}/eclv_ctc_expected.png', './eclv_ctc.png') - assert comparison.mssim == 1 - cleanup() + cleanup(expected_output) + eclv.main(f"{cwd}/{yaml_file}") + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_output)) diff --git a/test/ens_ss/custom2_ens_ss.yaml b/test/ens_ss/custom2_ens_ss.yaml deleted file mode 100644 index a01d1cbd..00000000 --- a/test/ens_ss/custom2_ens_ss.yaml +++ /dev/null @@ -1,120 +0,0 @@ -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -colors: -- '#ff0000' -- '#8000ff' - -create_html: 'False' -dump_points_1: 'True' -ensss_pts: -1 -ensss_pts_disp: 'True' -event_equal: 'True' -fcst_var_val_1: - TMP: - - MSE -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: [] -indy_vals: [] -indy_var: '' -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: None -list_stat_1: -- MSE -mar: -- 8 -- 4 -- 5 -- 4 -mgp: -- 1 -- 1 -- 0 -plot_caption: '' - -plot_disp: -- 'True' -- 'True' -plot_height: 8.5 -plot_res: 72 -plot_type: png16m -plot_units: in -plot_width: 11.0 -series_line_style: -- '-' -- '-' -series_line_width: -- 1 -- 1 -series_order: -- 1 -- 2 -series_symbols: -- . -- . -series_type: -- b -- b -series_val_1: - model: - - WRF -title: test title -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] - -xaxis: test x_label -xaxis_reverse: 'False' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 - -yaxis_1: test y_label -yaxis_2: '' -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 -y2lab_align: 0.5 -y2lab_offset: 1 -y2lab_size: 1 -y2lab_weight: 1 -y2lim: [] -y2tlab_horiz: 0.5 -y2tlab_orient: 1 -y2tlab_perp: 1 -y2tlab_size: 1.0 - -stat_input: !ENV '${TEST_DIR}/ens_ss.data' -plot_filename: !ENV '${TEST_DIR}/intermed_files/ens_ss.png' -points_path: !ENV '${TEST_DIR}/intermed_files' -show_legend: - -True - -True \ No newline at end of file diff --git a/test/ens_ss/custom_ens_ss.yaml b/test/ens_ss/custom_ens_ss.yaml index 3bfe0585..5d4dd003 100644 --- a/test/ens_ss/custom_ens_ss.yaml +++ b/test/ens_ss/custom_ens_ss.yaml @@ -54,11 +54,7 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -#points_path: /dir_to_save_points1_file +points_path: !ENV '${TEST_OUTPUT}/intermed_files' series_line_style: - '-' @@ -120,7 +116,7 @@ y2tlab_perp: 1 y2tlab_size: 1.0 stat_input: !ENV '${TEST_DIR}/ens_ss.data' -plot_filename: !ENV '${TEST_DIR}/ens_ss.png' +plot_filename: !ENV '${TEST_OUTPUT}/ens_ss.png' # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have # permissions to the directory you specify. The default, as specified in the default config file is stdout. diff --git a/test/ens_ss/test_ens_ss.py b/test/ens_ss/test_ens_ss.py index 9962fc56..d7205d8b 100644 --- a/test/ens_ss/test_ens_ss.py +++ b/test/ens_ss/test_ens_ss.py @@ -1,22 +1,16 @@ import os import pytest -#from metcalcpy.compare_images import CompareImages + from metplotpy.plots.ens_ss import ens_ss cwd = os.path.dirname(__file__) @pytest.fixture -def setup(): +def setup(module_setup_env): # Cleanup the plotfile and point1 output file from any previous run cleanup() - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_ens_ss.yaml" - # Invoke the command to generate a Bar plot based on - # the custom_ens_ss.yaml custom config file. + custom_config_filename = f"{cwd}/custom_ens_ss.yaml" ens_ss.main(custom_config_filename) @@ -25,64 +19,14 @@ def cleanup(): # from any previous runs try: plot_file = 'ens_ss.png' - points_file_1 = 'ens_ss.points1' - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(cwd, points_file_1)) - except OSError as er: - # Typically when files have already been removed or - # don't exist. Ignore. + points_file_1 = 'intermed_files/ens_ss.points1' + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) + os.remove(os.path.join(os.environ['TEST_OUTPUT'], points_file_1)) + except OSError: pass -@pytest.mark.parametrize("test_input, expected", - ([f"{cwd}/ens_ss.png", True],[f"{cwd}/ens_ss.png", True],[f"{cwd}/ens_ss.points1", True])) -def test_files_exist( setup, test_input, expected): - """ - Checking that the plot and data files are getting created - """ - assert os.path.isfile(test_input) == expected - cleanup() - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/ens_ss_expected.png', f'{cwd}/ens_ss.png') - assert comparison.mssim == 1 - cleanup() - - -@pytest.mark.parametrize("test_input, expected", - ([f"{cwd}/intermed_files/ens_ss.png", True], [f"{cwd}/intermed_files/ens_ss.points1", True])) -def test_files_exist(test_input, expected): - """ - Checking that the plot and data files are getting created - """ - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError as e: - pass - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom2_ens_ss.yaml" - - # Invoke the command to generate a Bar plot based on - # the custom_ens_ss.yaml custom config file. - ens_ss.main(custom_config_filename) - assert os.path.isfile(test_input) == expected - cleanup() - try: - subdir = os.path.join(cwd, "intermed_files") - plot_file = 'ens_ss.png' - points_file_1 = 'ens_ss.points1' - os.remove(os.path.join(subdir, plot_file)) - os.remove(os.path.join(subdir, points_file_1)) - os.rmdir(subdir) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - +def test_custom_ens_ss(setup): + """Checking that the plot and data files are getting created""" + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'ens_ss.png')) + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files', 'ens_ss.points1')) diff --git a/test/equivalence_testing_bounds/custom_equivalence_testing_bounds.yaml b/test/equivalence_testing_bounds/custom_equivalence_testing_bounds.yaml index ffec3e5d..0ac72840 100644 --- a/test/equivalence_testing_bounds/custom_equivalence_testing_bounds.yaml +++ b/test/equivalence_testing_bounds/custom_equivalence_testing_bounds.yaml @@ -163,7 +163,8 @@ ytlab_horiz: 0.5 ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 -plot_filename: !ENV '${TEST_DIR}/equivalence_testing_bounds.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' +plot_filename: !ENV '${TEST_OUTPUT}/equivalence_testing_bounds.png' stat_input: !ENV '${TEST_DIR}/equivalence_testing_bounds.data' show_legend: - 'True' diff --git a/test/equivalence_testing_bounds/custom_equivalence_testing_bounds2.yaml b/test/equivalence_testing_bounds/custom_equivalence_testing_bounds2.yaml deleted file mode 100644 index f087c6b8..00000000 --- a/test/equivalence_testing_bounds/custom_equivalence_testing_bounds2.yaml +++ /dev/null @@ -1,172 +0,0 @@ -alpha: 0.05 -box_avg: 'False' -box_boxwex: 0.2 -box_notch: 'False' -box_outline: 'True' -box_pts: 'False' -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -colors: -- '#ff0000' -- '#8000ff' -- '#00ff7f' -con_series: -- 1 -- 1 -- 1 -create_html: 'True' -derived_series_1: -- - ARWref_hrconus_control DPT ME - - ARWref_hrconus_experiment1 DPT ME - - ETB -derived_series_2: [] -dump_points_1: 'True' -dump_points_2: 'False' -event_equal: 'True' -fcst_var_val_1: - DPT: - - ME -fcst_var_val_2: {} -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -high_eqbound: 0.001 -indy_label: [] -indy_stagger_1: 'False' -indy_stagger_2: 'False' -indy_vals: [] -indy_var: fcst_lead -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: sl1l2 -list_stat_1: -- ME -list_stat_2: [] -list_static_val: - fcst_var: DPT -low_eqbound: -0.001 -mar: -- 8 -- 4 -- 5 -- 4 -method: bca -mgp: -- 1 -- 1 -- 0 -num_iterations: 1 -num_threads: -1 -plot_caption: '' -plot_ci: -- none -- none -- none -plot_disp: -- 'True' -- 'True' -- 'True' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' -random_seed: null -series_line_style: -- '-' -- '-' -- '-' -series_line_width: -- 1 -- 1 -- 1 -series_order: -- 1 -- 2 -- 3 -series_symbols: -- . -- . -- . -series_type: -- b -- b -- b -series_val_1: - model: - - ARWref_hrconus_control - - ARWref_hrconus_experiment1 -series_val_2: {} -show_nstats: 'False' -show_signif: -- 'False' -- 'False' -- 'False' -sync_yaxes: 'False' -title: test title -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] -variance_inflation_factor: 'False' -vert_plot: 'False' -x2lab_align: 0.5 -x2lab_offset: -0.5 -x2lab_size: 0.8 -x2lab_weight: 1 -x2tlab_horiz: 0.5 -x2tlab_orient: 1 -x2tlab_perp: 1 -x2tlab_size: 0.8 -xaxis: test x_label -xaxis_reverse: 'False' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -y2lab_align: 0.5 -y2lab_offset: 1 -y2lab_size: 1 -y2lab_weight: 1 -y2lim: [] -y2tlab_horiz: 0.5 -y2tlab_orient: 1 -y2tlab_perp: 1 -y2tlab_size: 1.0 -yaxis_1: test y_label -yaxis_2: '' -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 -plot_filename: !ENV '${TEST_DIR}/intermed_files/equivalence_testing_bounds.png' -stat_input: !ENV '${TEST_DIR}/equivalence_testing_bounds.data' -show_legend: -- 'True' -- 'True' -- 'True' diff --git a/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py b/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py index e7e849e6..a3f27872 100644 --- a/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py +++ b/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py @@ -1,74 +1,20 @@ import pytest import os from metplotpy.plots.equivalence_testing_bounds import equivalence_testing_bounds as etb -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['equivalence_testing_bounds.png', 'equivalence_testing_bounds.points1'] @pytest.fixture -def setup(remove_files, setup_env): +def setup(remove_files, module_setup_env): # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_equivalence_testing_bounds.yaml" + remove_files(os.environ['TEST_OUTPUT'], 'equivalence_testing_bounds.png') + remove_files(os.environ['TEST_OUTPUT'], 'intermed_files/equivalence_testing_bounds.points1') - # Invoke the command to generate an equivalence testing boundary plot based on - # the custom config file. + custom_config_filename = f"{cwd}/custom_equivalence_testing_bounds.yaml" etb.main(custom_config_filename) -@pytest.mark.parametrize("test_input,expected", - (["equivalence_testing_bounds.png", True], - ["equivalence_testing_bounds.points1", True])) -def test_files_exist(setup, test_input, expected, remove_files): - ''' - Checking that the plot and data files are getting created - ''' - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("skimage differences causing failure") -def test_images_match(setup, remove_files): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - plot_file = 'equivalence_testing_bounds.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/equivalence_testing_bounds_expected.png', actual_file) - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input,expected", - (["intermed_files/equivalence_testing_bounds.png", True], - ["intermed_files/equivalence_testing_bounds.points1", True])) -def test_files_exist(setup_env, test_input, expected, remove_files): - ''' - Checking that the plot and data files are getting created - ''' - - intermed_dir = os.path.join(cwd, 'intermed_files') - try: - os.mkdir(intermed_dir) - except FileExistsError: - pass - - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_equivalence_testing_bounds2.yaml" - - # Invoke the command to generate an equivalence testing boundary plot based on - # the custom config file. - etb.main(custom_config_filename) - - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, ['intermed_files/equivalence_testing_bounds.png', - 'intermed_files/equivalence_testing_bounds.points1', - 'intermed_files/equivalence_testing_bounds.html']) - try: - os.rmdir(intermed_dir) - except OSError: - pass +def test_files_exist(setup): + """Checking that the plot and data files are getting created""" + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/equivalence_testing_bounds.png") + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/intermed_files/equivalence_testing_bounds.points1") diff --git a/test/histogram/prob_hist.yaml b/test/histogram/prob_hist.yaml index 1b72e5c2..f4610ca0 100644 --- a/test/histogram/prob_hist.yaml +++ b/test/histogram/prob_hist.yaml @@ -1,5 +1,5 @@ stat_input: !ENV '${TEST_DIR}/prob_hist.data' -plot_filename: !ENV '${TEST_DIR}/prob_hist.png' +plot_filename: !ENV '${TEST_OUTPUT}/prob_hist.png' caption_align: 0.0 caption_col: '#333333' diff --git a/test/histogram/rank_hist.yaml b/test/histogram/rank_hist.yaml index 8cf23ec9..f1899b45 100644 --- a/test/histogram/rank_hist.yaml +++ b/test/histogram/rank_hist.yaml @@ -1,5 +1,5 @@ stat_input: !ENV '${TEST_DIR}/rank_hist.data' -plot_filename: !ENV '${TEST_DIR}/rank_hist.png' +plot_filename: !ENV '${TEST_OUTPUT}/rank_hist.png' caption_align: 0.0 caption_col: '#333333' diff --git a/test/histogram/rel_hist.yaml b/test/histogram/rel_hist.yaml index ebb76e03..e6806307 100644 --- a/test/histogram/rel_hist.yaml +++ b/test/histogram/rel_hist.yaml @@ -1,4 +1,4 @@ -plot_filename: !ENV '${TEST_DIR}/rel_hist.png' +plot_filename: !ENV '${TEST_OUTPUT}/rel_hist.png' stat_input: !ENV '${TEST_DIR}/rel_hist.data' diff --git a/test/histogram/test_prob_hist.py b/test/histogram/test_prob_hist.py index fc9b3ab4..41fb9bcf 100644 --- a/test/histogram/test_prob_hist.py +++ b/test/histogram/test_prob_hist.py @@ -1,15 +1,13 @@ import pytest import os from metplotpy.plots.histogram import prob_hist -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) @pytest.fixture -def setup(setup_env): +def setup(module_setup_env): cleanup() - setup_env(cwd) custom_config_filename = f"{cwd}/prob_hist.yaml" prob_hist.main(custom_config_filename) @@ -20,29 +18,13 @@ def cleanup(): # from any previous runs try: plot_file = 'prob_hist.png' - os.remove(os.path.join(cwd, plot_file)) + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) except OSError: pass -@pytest.mark.parametrize("test_input, expected", - (["prob_hist_expected.png", True], - ["prob_hist.png", True])) -def test_files_exist(setup, test_input, expected): +def test_files_exist(setup): """ Checking that the plot and data files are getting created """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - cleanup() - -@pytest.mark.skip("Image comparisons fail during Github Actions checks.") -def test_images_match(setup): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f"{cwd}/prob_hist_expected.png", - f"{cwd}/prob_hist.png") - assert comparison.mssim == 1 - cleanup() + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/prob_hist.png") diff --git a/test/histogram/test_rank_hist.py b/test/histogram/test_rank_hist.py index 4b9d5b08..75f19bde 100644 --- a/test/histogram/test_rank_hist.py +++ b/test/histogram/test_rank_hist.py @@ -1,16 +1,13 @@ import pytest import os from metplotpy.plots.histogram import rank_hist -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) @pytest.fixture -def setup(setup_env): +def setup(module_setup_env): cleanup() - setup_env(cwd) - custom_config_filename = f"{cwd}/rank_hist.yaml" rank_hist.main(custom_config_filename) @@ -20,30 +17,13 @@ def cleanup(): # from any previous runs try: plot_file = 'rank_hist.png' - os.remove(os.path.join(cwd, plot_file)) + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) except OSError: pass -@pytest.mark.parametrize("test_input, expected", - (["rank_hist_expected.png", True], - ["rank_hist.png", True])) -def test_files_exist(setup, test_input, expected): +def test_files_exist(setup): """ Checking that the plot and data files are getting created """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - cleanup() - - -@pytest.mark.skip("Image comparisons fail during Github Actions checks.") -def test_images_match(setup): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f"{cwd}/rank_hist_expected.png", - f"{cwd}/rank_hist.png") - assert comparison.mssim == 1 - cleanup() + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/rank_hist.png") diff --git a/test/histogram/test_rel_hist.py b/test/histogram/test_rel_hist.py index a491283c..ad3dfd8e 100644 --- a/test/histogram/test_rel_hist.py +++ b/test/histogram/test_rel_hist.py @@ -1,20 +1,15 @@ import pytest import os from metplotpy.plots.histogram import rel_hist -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) @pytest.fixture -def setup(setup_env): +def setup(module_setup_env): # Cleanup the plotfile output file from any previous run cleanup() - setup_env(cwd) custom_config_filename = f"{cwd}/rel_hist.yaml" - - # Invoke the command to generate a histogram based on - # the rel_hist.yaml custom config file. rel_hist.main(custom_config_filename) @@ -23,30 +18,13 @@ def cleanup(): # from any previous runs try: plot_file = 'rel_hist.png' - os.remove(os.path.join(cwd, plot_file)) + os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) except OSError: pass -@pytest.mark.parametrize("test_input, expected", - (["rel_hist_expected.png", True], - ["rel_hist.png", True])) -def test_files_exist(setup, test_input, expected): +def test_files_exist(setup): """ Checking that the plot and data files are getting created """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - cleanup() - - -@pytest.mark.skip("Image comparisons fail in Github Actions checks.") -def test_images_match(setup): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f"{cwd}/rel_hist_expected.png", - f"{cwd}/rel_hist.png") - assert comparison.mssim == 1 - cleanup() + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/rel_hist.png") diff --git a/test/histogram_2d/custom_histogram_2d.yaml b/test/histogram_2d/custom_histogram_2d.yaml index 292349c2..6bf8c366 100644 --- a/test/histogram_2d/custom_histogram_2d.yaml +++ b/test/histogram_2d/custom_histogram_2d.yaml @@ -1,5 +1,5 @@ # replace with the location and name of your choosing: -plot_filename: !ENV '${TEST_DIR}/custom_tmp_z2_p500.png' +plot_filename: !ENV '${TEST_OUTPUT}/custom_tmp_z2_p500.png' height: 800 width: 1200 diff --git a/test/histogram_2d/minimal_histogram_2d.yaml b/test/histogram_2d/minimal_histogram_2d.yaml index 291feb3b..6e9264b3 100644 --- a/test/histogram_2d/minimal_histogram_2d.yaml +++ b/test/histogram_2d/minimal_histogram_2d.yaml @@ -2,4 +2,4 @@ # configuration file stat_input: !ENV '${TEST_DIR}/grid_diag_temperature.nc' -plot_filename: !ENV '${TEST_DIR}/tmp_z2_p500.png' +plot_filename: !ENV '${TEST_OUTPUT}/tmp_z2_p500.png' diff --git a/test/histogram_2d/test_histogram_2d.py b/test/histogram_2d/test_histogram_2d.py index 1bc96a04..3bc7b9e3 100644 --- a/test/histogram_2d/test_histogram_2d.py +++ b/test/histogram_2d/test_histogram_2d.py @@ -3,36 +3,22 @@ from metplotpy.plots.histogram_2d import histogram_2d as h2d -# from metcalcpy.compare_images import CompareImages - cwd = os.path.dirname(__file__) - -@pytest.fixture -def setup(setup_env): - # Cleanup the plotfile and point1 output file from any previous run - # cleanup() - setup_env(cwd) - custom_config_filename = f"{cwd}/minimal_histogram_2d.yaml" - # print("\n current directory: ", os.getcwd()) - # print("\ncustom config file: ", custom_config_filename, '\n') - - # Invoke the command to generate a histogram_2d plot based on - # the settings in the "defaults" config file. - h2d.main(custom_config_filename) - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # !!!!!!!!! IMPORTANT !!!!!! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # change the stat_input and plot_filename to explicitly point to this directory before # running the test below because xarray cannot handle relative paths when reading in # filenames -def test_plot_exists(setup, remove_files): - ''' - Checking that only the "defaults" plot file is getting created - - ''' - test_input = "tmp_z2_p500.png" - assert os.path.exists(f"{cwd}/{test_input}") - remove_files(cwd, ['tmp_z2_p500.png']) +@pytest.mark.parametrize( + "yaml_file, expected_output", [ + ("minimal_histogram_2d.yaml", "tmp_z2_p500.png"), + ("custom_histogram_2d.yaml", "custom_tmp_z2_p500.png"), + ] +) +def test_plot_exists(module_setup_env, yaml_file, expected_output, remove_files): + """Checking that only the "defaults" plot file is getting created""" + remove_files(os.environ['TEST_OUTPUT'], [expected_output]) + h2d.main(os.path.join(cwd, yaml_file)) + assert os.path.exists(f"{os.environ['TEST_OUTPUT']}/{expected_output}") diff --git a/test/import/test_import.py b/test/import/test_import.py index 9a6d304a..7dc82123 100644 --- a/test/import/test_import.py +++ b/test/import/test_import.py @@ -8,7 +8,6 @@ def test_import_hovmoeller(): import metplotpy.plots.hovmoeller.hovmoeller except: assert False - assert True # used in METplus use case: model_applications/medium_range/UserScript_fcstGEFS_Difficulty_Index @@ -18,7 +17,6 @@ def test_import_difficulty_index(): from metplotpy.plots.difficulty_index.plot_difficulty_index import plot_field except: assert False - assert True def test_import_all(): @@ -26,4 +24,3 @@ def test_import_all(): import metplotpy except: assert False - assert True From d92048520528c8c1e7f7fa48634e713fb99e0606 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:01:13 -0700 Subject: [PATCH 03/31] create directory for pointsN file creation so it doesn't fail when directory doesn't already exist --- metplotpy/plots/bar/bar.py | 3 ++- metplotpy/plots/box/box.py | 3 +++ metplotpy/plots/contour/contour.py | 1 + metplotpy/plots/eclv/eclv.py | 1 + metplotpy/plots/ens_ss/ens_ss.py | 1 + .../equivalence_testing_bounds/equivalence_testing_bounds.py | 1 + metplotpy/plots/histogram/hist.py | 1 + 7 files changed, 10 insertions(+), 1 deletion(-) diff --git a/metplotpy/plots/bar/bar.py b/metplotpy/plots/bar/bar.py index cb881886..cd5b30a7 100644 --- a/metplotpy/plots/bar/bar.py +++ b/metplotpy/plots/bar/bar.py @@ -498,10 +498,11 @@ def write_output_file(self) -> None: filename = filename + '.points1' + # create directory if needed + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as f: for series in self.series_list: f.write(f"{series.series_points['dbl_med']}\n") - f.close() def main(config_filename=None): diff --git a/metplotpy/plots/box/box.py b/metplotpy/plots/box/box.py index 6c41762a..50d8d2a1 100644 --- a/metplotpy/plots/box/box.py +++ b/metplotpy/plots/box/box.py @@ -605,6 +605,9 @@ def write_output_file(self) -> None: filename = filename + '.points1' if os.path.exists(filename): os.remove(filename) + # create directory if needed + os.makedirs(os.path.dirname(filename), exist_ok=True) + for series in self.series_list: for indy_val in self.config_obj.indy_vals: if calc_util.is_string_integer(indy_val): diff --git a/metplotpy/plots/contour/contour.py b/metplotpy/plots/contour/contour.py index 353a1d72..ad84149d 100644 --- a/metplotpy/plots/contour/contour.py +++ b/metplotpy/plots/contour/contour.py @@ -408,6 +408,7 @@ def write_output_file(self) -> None: filename = self.config_obj.points_path + os.path.sep + filename filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as file: writer = csv.writer(file, delimiter='\t') diff --git a/metplotpy/plots/eclv/eclv.py b/metplotpy/plots/eclv/eclv.py index 5860b572..358539d6 100644 --- a/metplotpy/plots/eclv/eclv.py +++ b/metplotpy/plots/eclv/eclv.py @@ -362,6 +362,7 @@ def write_output_file(self) -> None: filename = self.config_obj.points_path + os.path.sep + filename filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as file: writer = csv.writer(file, delimiter='\t') diff --git a/metplotpy/plots/ens_ss/ens_ss.py b/metplotpy/plots/ens_ss/ens_ss.py index d6d153ee..7714e07b 100644 --- a/metplotpy/plots/ens_ss/ens_ss.py +++ b/metplotpy/plots/ens_ss/ens_ss.py @@ -525,6 +525,7 @@ def write_output_file(self) -> None: # filename = 'points' filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as file: while i < len(self.series_list): diff --git a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py index 5d3798c0..f0d18458 100644 --- a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py +++ b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py @@ -556,6 +556,7 @@ def write_output_file(self) -> None: filename = self.config_obj.points_path + os.path.sep + filename filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) # save points self._save_points(ci_tost_df.values.tolist(), filename) diff --git a/metplotpy/plots/histogram/hist.py b/metplotpy/plots/histogram/hist.py index a3012a5c..9ed3e6ca 100644 --- a/metplotpy/plots/histogram/hist.py +++ b/metplotpy/plots/histogram/hist.py @@ -472,6 +472,7 @@ def write_output_file(self) -> None: filename = self.config_obj.points_path + os.path.sep + filename filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as file: for series in self.series_list: From 5bdec44312bd3b6d5d354292b3e96b76de65d76c Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:40:26 -0700 Subject: [PATCH 04/31] fix bug in write_html and remove_html functions that cause output filename to be .html instead of the correct path -- consider moving these functions to the base class, although they will likely be removed when plotly is removed --- metplotpy/plots/bar/bar.py | 10 +++++----- metplotpy/plots/box/box.py | 5 ++--- metplotpy/plots/contour/contour.py | 10 +++++----- metplotpy/plots/ens_ss/ens_ss.py | 10 +++++----- .../equivalence_testing_bounds.py | 10 +++++----- metplotpy/plots/histogram/hist.py | 5 ++--- metplotpy/plots/hovmoeller/hovmoeller.py | 4 ++-- metplotpy/plots/line/line.py | 10 +++++----- metplotpy/plots/reliability_diagram/reliability.py | 9 +++++---- metplotpy/plots/roc_diagram/roc_diagram.py | 4 ++-- 10 files changed, 38 insertions(+), 39 deletions(-) diff --git a/metplotpy/plots/bar/bar.py b/metplotpy/plots/bar/bar.py index cd5b30a7..77f3dab5 100644 --- a/metplotpy/plots/bar/bar.py +++ b/metplotpy/plots/bar/bar.py @@ -452,8 +452,9 @@ def _remove_html(self) -> None: Removes previously made HTML file. """ - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" + # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -465,9 +466,8 @@ def write_html(self) -> None: """ if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/box/box.py b/metplotpy/plots/box/box.py index 50d8d2a1..35e9f522 100644 --- a/metplotpy/plots/box/box.py +++ b/metplotpy/plots/box/box.py @@ -569,9 +569,8 @@ def write_html(self) -> None: # is_create = self.config_obj.create_html if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/contour/contour.py b/metplotpy/plots/contour/contour.py index ad84149d..233b6e47 100644 --- a/metplotpy/plots/contour/contour.py +++ b/metplotpy/plots/contour/contour.py @@ -365,8 +365,9 @@ def _remove_html(self) -> None: Removes previously made HTML file. """ - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" + # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -377,9 +378,8 @@ def write_html(self) -> None: """ if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/ens_ss/ens_ss.py b/metplotpy/plots/ens_ss/ens_ss.py index 7714e07b..f74d5637 100644 --- a/metplotpy/plots/ens_ss/ens_ss.py +++ b/metplotpy/plots/ens_ss/ens_ss.py @@ -475,8 +475,9 @@ def _remove_html(self) -> None: Removes previously made HTML file. """ - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" + # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -487,9 +488,8 @@ def write_html(self) -> None: """ if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py index f0d18458..7cb366fa 100644 --- a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py +++ b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py @@ -488,9 +488,9 @@ def _remove_html(self) -> None: """ Removes previously made HTML file. """ + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -504,9 +504,9 @@ def write_html(self) -> None: self.logger.info(f"Write html file: {datetime.now()}") if self.config_obj.create_html is True: - # construct the fle name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + # construct the file name from plot_filename + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/histogram/hist.py b/metplotpy/plots/histogram/hist.py index 9ed3e6ca..fe12d96e 100644 --- a/metplotpy/plots/histogram/hist.py +++ b/metplotpy/plots/histogram/hist.py @@ -437,9 +437,8 @@ def write_html(self) -> None: if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/hovmoeller/hovmoeller.py b/metplotpy/plots/hovmoeller/hovmoeller.py index b189a903..f38a494f 100644 --- a/metplotpy/plots/hovmoeller/hovmoeller.py +++ b/metplotpy/plots/hovmoeller/hovmoeller.py @@ -208,8 +208,8 @@ def write_html(self) -> None: self.logger.info(f"Begin writing html output: {datetime.now()}") if self.config_obj.create_html is True: # construct the fle name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/line/line.py b/metplotpy/plots/line/line.py index c49c3614..af5eb3de 100644 --- a/metplotpy/plots/line/line.py +++ b/metplotpy/plots/line/line.py @@ -712,8 +712,9 @@ def _remove_html(self) -> None: Removes previously made HTML file. """ - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" + # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -728,9 +729,8 @@ def write_html(self) -> None: logger.info(f"Begin writing to html file: {datetime.now()}") if self.config_obj.create_html is True: # construct the file name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - name_arr[-1] = 'html' - html_name = ".".join(name_arr) + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/reliability_diagram/reliability.py b/metplotpy/plots/reliability_diagram/reliability.py index b83f9572..aebd0fa9 100644 --- a/metplotpy/plots/reliability_diagram/reliability.py +++ b/metplotpy/plots/reliability_diagram/reliability.py @@ -600,8 +600,9 @@ def _remove_html(self) -> None: Removes previously made HTML file. """ - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" + # remove the old file if it exist if os.path.exists(html_name): os.remove(html_name) @@ -613,8 +614,8 @@ def write_html(self) -> None: self.logger.info("Writing html file.") if self.config_obj.create_html is True: # construct the fle name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) diff --git a/metplotpy/plots/roc_diagram/roc_diagram.py b/metplotpy/plots/roc_diagram/roc_diagram.py index c728e0c3..8dea0e69 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram.py +++ b/metplotpy/plots/roc_diagram/roc_diagram.py @@ -545,8 +545,8 @@ def write_html(self) -> None: self.logger.info("Writing HTML file") if self.config_obj.create_html is True: # construct the fle name from plot_filename - name_arr = self.get_config_value('plot_filename').split('.') - html_name = name_arr[0] + ".html" + base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) + html_name = f"{base_name}.html" # save html self.figure.write_html(html_name, include_plotlyjs=False) From 0e1ba740ac1e0ccfe68e56801d227b57f0beba95 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:42:41 -0700 Subject: [PATCH 05/31] use os.makedirs to ensure that parent directories are handled --- metplotpy/plots/base_plot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metplotpy/plots/base_plot.py b/metplotpy/plots/base_plot.py index 03a84ef1..6925ce64 100644 --- a/metplotpy/plots/base_plot.py +++ b/metplotpy/plots/base_plot.py @@ -387,8 +387,7 @@ def save_to_file(self): # Create the directory for the output plot if it doesn't already exist dirname = os.path.dirname(os.path.abspath(image_name)) - if not os.path.exists(dirname): - os.mkdir(dirname) + os.makedirs(dirname, exist_ok=True) if self.figure: try: self.figure.write_image(image_name) From 31f99358a0f5b60fd4c845f44a70b6e3be9ed3a1 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:43:52 -0700 Subject: [PATCH 06/31] PyCharm files that we may want to ignore --- .idea/METplotpy.iml | 2 +- .idea/misc.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/METplotpy.iml b/.idea/METplotpy.iml index 2d40d024..a2a889b2 100644 --- a/.idea/METplotpy.iml +++ b/.idea/METplotpy.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index c58198b3..b84ee64d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file From 22418da920965bd99326e67db0bf888cf5f78a15 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:44:50 -0700 Subject: [PATCH 07/31] create directory before writing file to prevent failure when directory does not exist --- metplotpy/plots/histogram_2d/histogram_2d.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/metplotpy/plots/histogram_2d/histogram_2d.py b/metplotpy/plots/histogram_2d/histogram_2d.py index 3f22aa06..f12d6cf6 100644 --- a/metplotpy/plots/histogram_2d/histogram_2d.py +++ b/metplotpy/plots/histogram_2d/histogram_2d.py @@ -109,15 +109,16 @@ def save_to_file(self): self.logger.info(f"Saving plot to file {image_name}: {datetime.now()} ") if self.figure: try: + os.makedirs(os.path.dirname(image_name), exist_ok=True) self.figure.write_image(image_name) except FileNotFoundError: self.logger.error(f"FileNotFoundError: Can't save to file {image_name}") - except ValueError: + except ValueError as err: self.logger.error(f"ValueError: Some other error occurred " - f"{datetime.now()}") + f"{datetime.now()}: {err}") else: - self.logger.error(f"The figure was not created. Cannot save file.") + self.logger.error("The figure was not created. Cannot save file.") self.logger.info(f"Finished saving plot to file: {datetime.now()}") From 8d992610673e674c5f7ae2895dfd13e7d5bfa318 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:45:30 -0700 Subject: [PATCH 08/31] remove logic that appears to not have any purpose --- metplotpy/plots/histogram_2d/histogram_2d.py | 42 +------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/metplotpy/plots/histogram_2d/histogram_2d.py b/metplotpy/plots/histogram_2d/histogram_2d.py index f12d6cf6..b7f35159 100644 --- a/metplotpy/plots/histogram_2d/histogram_2d.py +++ b/metplotpy/plots/histogram_2d/histogram_2d.py @@ -129,46 +129,8 @@ def write_output_file(self): :return: """ - self.logger.info(f"Begin writing plot to output file: {datetime.now()}") - self.logger.info(f"No intermediate points1 file created. This plot type is not " - "integrated into METviewer: {datetime.now()}") - - # if points_path parameter doesn't exist, - # open file, name it based on the stat_input config setting, - # (the input data file) except replace the .data - # extension with .points1 extension - # otherwise use points_path path - match = re.match(r'(.*)(.data)', self.config_obj.parameters['stat_input']) - if self.config_obj.dump_points_1 is True and match: - filename = match.group(1) - # replace the default path with the custom - if self.config_obj.points_path is not None: - # get the file name - path = filename.split(os.path.sep) - if len(path) > 0: - filename = path[-1] - else: - filename = '.' + os.path.sep - filename = self.config_obj.points_path + os.path.sep + filename - - output_file = filename + '.points1' - - # make sure this file doesn't already - # exist, delete it if it does - self.logger.info(f"Check if file exists, delete if it does. " - f"{datetime.now()}") - try: - if os.stat(output_file).st_size == 0: - open(output_file, 'a') - else: - os.remove(output_file) - except FileNotFoundError: - # OK if no file was found - self.logger.info(f"FileNotFound while checking if output file exists. " - f" This is OK:{datetime.now()}") - pass - - self.logger.info(f"Finished writing plot to output file: {datetime.now()}") + self.logger.info("No intermediate points1 file created. This plot type is not " + f"integrated into METviewer: {datetime.now()}") def _read_input_data(self): """ From 85e0cf418300f33e7f1b450cc869054ac5511502 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:46:39 -0700 Subject: [PATCH 09/31] improve logging when verifying config values to alert users which parameters have the incorrect number of values --- metplotpy/plots/line/line.py | 4 +-- metplotpy/plots/line/line_config.py | 43 ++++++++++++++++------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/metplotpy/plots/line/line.py b/metplotpy/plots/line/line.py index af5eb3de..b978049c 100644 --- a/metplotpy/plots/line/line.py +++ b/metplotpy/plots/line/line.py @@ -76,8 +76,8 @@ def __init__(self, parameters: dict) -> None: "curves is inconsistent with the number of settings " "required for describing each series. Please check " "the number of your configuration file's plot_ci, " - "plot_disp, series_order, user_legend " - "colors, series_symbols, show_legend settings.") + "plot_disp, series_order, user_legend, " + "colors, series_symbols, and show_legend settings.") self.logger.error(f"ValueError: {error_msg}: {datetime.now()}") raise ValueError(error_msg) diff --git a/metplotpy/plots/line/line_config.py b/metplotpy/plots/line/line_config.py index d4acbe88..39149d96 100644 --- a/metplotpy/plots/line/line_config.py +++ b/metplotpy/plots/line/line_config.py @@ -383,7 +383,7 @@ def _get_plot_stat(self) -> str: def _config_consistency_check(self) -> bool: """ Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols + plot_disp, series_order, user_legend, colors, and series_symbols are consistent. Args: @@ -399,24 +399,29 @@ def _config_consistency_check(self) -> bool: # permutations from the series_var setting in the # config file - # Numbers of values for other settings for series - num_ci_settings = len(self.plot_ci) - num_plot_disp = len(self.plot_disp) - num_markers = len(self.marker_list) - num_series_ord = len(self.series_ordering) - num_colors = len(self.colors_list) - num_legends = len(self.user_legends) - num_line_widths = len(self.linewidth_list) - num_linestyles = len(self.linestyles_list) - num_show_legend = len(self.show_legend) - num_con_series = len(self.con_series) - status = False - - if (self.num_series == num_plot_disp == \ - num_markers == num_series_ord == num_colors \ - == num_legends == num_line_widths == num_linestyles == num_ci_settings \ - == num_show_legend == num_con_series): - status = True + lists_to_check = { + "plot_ci": self.plot_ci, + "plot_disp": self.plot_disp, + "marker_list": self.marker_list, + "series_ordering": self.series_ordering, + "colors_list": self.colors_list, + "user_legends": self.user_legends, + "linewidth_list": self.linewidth_list, + "linestyles_list": self.linestyles_list, + "show_legend": self.show_legend, + "con_series": self.con_series, + } + status = True + for name, list_to_check in lists_to_check.items(): + + if len(list_to_check) == self.num_series: + continue + + self.logger.error( + f"number of series ({self.num_series}) does not match {name} ({len(list_to_check)})" + ) + status = False + return status def _get_plot_ci(self) -> list: From c8d7bc674b57d7c5e4efa7cfb662c7c1561c7b86 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:49:28 -0700 Subject: [PATCH 10/31] include log output when tests fail for easier debugging, do not create test output directory because plotting functions should now create the directories before writing files, only log that intermed_files directory is being removed if it exists --- test/conftest.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index f3a72bdb..c281f389 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,6 +4,7 @@ import sys from pathlib import Path import shutil +import logging import json import xarray as xr from pandas import DatetimeIndex @@ -113,7 +114,6 @@ def module_setup_env(request): os.path.join(test_dir, os.pardir, 'test_output')) # write to a subdirectory named after the plot type os.environ['TEST_OUTPUT'] = os.path.join(output_dir, os.path.basename(test_dir)) - os.makedirs(os.environ['TEST_OUTPUT'], exist_ok=True) yield # Optional: cleanup after all tests in the module complete @@ -132,11 +132,12 @@ def remove_the_files(test_dir, file_list): pass # also remove intermed_files directory if it exists - print("Removing intermed_files directory if it exists") - try: - shutil.rmtree(f"{test_dir}/intermed_files") - except FileNotFoundError: - pass + if os.path.isdir(f"{test_dir}/intermed_files"): + print("Removing intermed_files directory") + try: + shutil.rmtree(f"{test_dir}/intermed_files") + except FileNotFoundError: + pass return remove_the_files @@ -171,3 +172,6 @@ def nc_test_file(tmp_path_factory): TEST_NC_DATA.to_netcdf(file_name) return file_name +@pytest.fixture(autouse=True) +def setup_logging(caplog): + caplog.set_level(logging.DEBUG) From 89fcdd86f2ca28fa4f13b8136e47e0ef4e0a8a7a Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:50:16 -0700 Subject: [PATCH 11/31] delete file that was accidentally committed --- test/equivalence_testing_bounds/.html | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 test/equivalence_testing_bounds/.html diff --git a/test/equivalence_testing_bounds/.html b/test/equivalence_testing_bounds/.html deleted file mode 100644 index 4804a2c5..00000000 --- a/test/equivalence_testing_bounds/.html +++ /dev/null @@ -1,24 +0,0 @@ - - - -
- - -
- -
- - \ No newline at end of file From 8db86384098427946767d8ebaaad5eb61843d7a9 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:40:57 -0700 Subject: [PATCH 12/31] add missing import --- metplotpy/plots/hovmoeller/hovmoeller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/metplotpy/plots/hovmoeller/hovmoeller.py b/metplotpy/plots/hovmoeller/hovmoeller.py index f38a494f..89145be3 100644 --- a/metplotpy/plots/hovmoeller/hovmoeller.py +++ b/metplotpy/plots/hovmoeller/hovmoeller.py @@ -17,15 +17,14 @@ __author__ = 'David Fillmore' __version__ = '0.1.0' -import metcalcpy.util.read_env_vars_in_config - """ Import standard modules """ +import os from datetime import datetime import getpass import sys -import yaml + import numpy as np import xarray as xr import plotly.graph_objects as go From acd128390aca0509a12738477104a5089a9f9609 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:42:43 -0700 Subject: [PATCH 13/31] removed files that were not allowing hovmoeller tests to find top level conftest functions -- presume warnings that were supressed here are no longer relevant --- test/hovmoeller/conftest.py | 135 ------------------------------------ test/hovmoeller/pytest.ini | 20 ------ 2 files changed, 155 deletions(-) delete mode 100644 test/hovmoeller/conftest.py delete mode 100644 test/hovmoeller/pytest.ini diff --git a/test/hovmoeller/conftest.py b/test/hovmoeller/conftest.py deleted file mode 100644 index 264258c2..00000000 --- a/test/hovmoeller/conftest.py +++ /dev/null @@ -1,135 +0,0 @@ -import pytest -import os -from unittest.mock import patch -import shutil -import json -import xarray as xr -from pandas import DatetimeIndex - -# This fixture temporarily sets the working directory -# to the dir containing the test file. This means -# realative file locations can be used for each test -# file. -# NOTE: autouse=True means this applies to ALL tests. -# Code that updates the cwd inside test is now redundant -# and can be deleted. -@pytest.fixture(autouse=True) -def change_test_dir(request, monkeypatch): - monkeypatch.chdir(request.fspath.dirname) - - -@pytest.fixture(autouse=True) -def patch_CompareImages(request): - """This fixture controls the use of CompareImages in the - test suite. By default, all calls to CompareImages will - result in the test skipping. To change this behaviour set - an env var METPLOTPY_COMPAREIMAGES - """ - if bool(os.getenv("METPLOTPY_COMPAREIMAGES")): - yield - else: - class mock_CompareImages: - def __init__(self, img1, img2): - # TODO: rather than skip we could inject an alternative - # comparison that is more relaxed. To do this, extend - # this this class to generate a self.mssim value. - pytest.skip("CompareImages not enabled in pytest. " - "To enable `export METPLOTPY_COMPAREIMAGES=$true`") - try: - with patch.object(request.module, 'CompareImages', mock_CompareImages) as mock_ci: - yield mock_ci - except AttributeError: - # test module doesn't import CompareImages. Do nothing. - yield - - -def ordered(obj): - """Recursive function to sort JSON, even lists of dicts with the same keys""" - if isinstance(obj, dict): - return sorted((k, ordered(v)) for k, v in obj.items()) - if isinstance(obj, list): - return sorted(ordered(x) for x in obj) - else: - return obj - -@pytest.fixture -def assert_json_equal(): - def compare_json(fig, expected_json_file): - """Takes a plotly figure and a json file - """ - # Treat everything as str for comparison purposes. - actual = json.loads(fig.to_json(), parse_float=str, parse_int=str) - with open(expected_json_file) as f: - expected = json.load(f,parse_float=str, parse_int=str) - # Fail with a nice message - if ordered(actual) == ordered(expected): - return True - else: - message = "This test will fail when there have been changes to plot code but the corresponding" \ - "json test file hasn't been updates. To update the test file run `fig.write_json`"\ - " e.g. `scatter.figure.write_json('custom_scatter_expected.json')`" - raise AssertionError(message) - - return compare_json - - -@pytest.fixture -def setup_env(): - def set_environ(test_dir): - print("Setting up environment") - os.environ['METPLOTPY_BASE'] = f"{test_dir}/../../" - os.environ['TEST_DIR'] = test_dir - return set_environ - - -@pytest.fixture() -def remove_files(): - def remove_the_files(test_dir, file_list): - print("Removing the files") - # loop over list of files under test_dir and remove them - for file in file_list: - try: - os.remove(os.path.join(test_dir, file)) - except OSError: - pass - - # also remove intermed_files directory if it exists - print("Removing intermed_files directory if it exists") - try: - shutil.rmtree(f"{test_dir}/intermed_files") - except FileNotFoundError: - pass - - return remove_the_files - - -# data for netCDF file -TEST_NC_DATA = xr.Dataset( - { - "precip": xr.DataArray( - [ - [[0.1, 0.2, 0.3], [0, 1.3, 4], [0, 20, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - ], - coords={ - "lat": [-1, 0, 1], - "lon": [112, 113, 114], - "time": DatetimeIndex(["2024-09-25 00:00:00", "2024-09-25 03:00:33"]), - }, - dims=["time", "lat", "lon"], - attrs={"long_name": "variable long name"}, - ), - }, - attrs={"Conventions": "CF-99.9", "history": "History string"}, -) - -@pytest.fixture() -def nc_test_file(tmp_path_factory): - """Create a netCDF file with a very small amount of data. - File is written to a temp directory and the path to the - file returned as the fixture value. - """ - file_name = tmp_path_factory.mktemp("data") / "test_data.nc" - TEST_NC_DATA.to_netcdf(file_name) - return file_name - diff --git a/test/hovmoeller/pytest.ini b/test/hovmoeller/pytest.ini deleted file mode 100644 index c339546e..00000000 --- a/test/hovmoeller/pytest.ini +++ /dev/null @@ -1,20 +0,0 @@ -[pytest] - # Deprecation warnings suppressed. - - # DeprecationWarning in numpy: parsing timezone aware datetimes is deprecated; - # this will raise an error in the future: - # hovmoeller_plotly.py:129: DeprecationWarning: - # - # parsing timezone aware datetimes is deprecated; this will raise an error in the future - - # Second DeprecationWarning in netCDF4: - # netCDF4_.py: 405: DeprecationWarning: - # - # tostring() is deprecated.Use - # tobytes() - # instead. -filterwarnings = - ignore - default:::netCDF4.* - ignore - default:::numpy.* \ No newline at end of file From f15fea4ebaf79b900bf8f585680815daf7957204 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:02:26 -0700 Subject: [PATCH 14/31] only capture INFO or lower logs to avoid excessive DEBUG logs from external packages --- test/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index c281f389..2aa40606 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -121,7 +121,7 @@ def module_setup_env(request): @pytest.fixture() def remove_files(): def remove_the_files(test_dir, file_list): - print("Removing the files") + print("Removing files") # loop over list of files under test_dir and remove them if isinstance(file_list, str): file_list = [file_list] @@ -174,4 +174,4 @@ def nc_test_file(tmp_path_factory): @pytest.fixture(autouse=True) def setup_logging(caplog): - caplog.set_level(logging.DEBUG) + caplog.set_level(logging.INFO) From ec77d7bf076f3f36410361ea81fad5dc5c12f792 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:03:35 -0700 Subject: [PATCH 15/31] more updates to tests to write to output directory, removed redundant tests, clean up broke yaml files (formatting of show_legend values) --- test/histogram/test_rel_hist.py | 2 +- test/hovmoeller/custom_hovmoeller.yaml | 2 +- test/hovmoeller/minimal_hovmoeller.yaml | 2 +- test/hovmoeller/test_hovmoeller.py | 93 +-- test/line/custom_line.yaml | 8 +- test/line/custom_line2.yaml | 4 +- test/line/custom_line_from_zero.yaml | 17 +- test/line/custom_line_groups.yaml | 17 +- test/line/custom_line_groups2.yaml | 13 +- test/line/env_fcst_var.yaml | 16 +- test/line/fbias_fixed_vars_vals.yaml | 17 +- test/line/mv_custom_vert_line.yaml | 16 +- test/line/test_line_groups_plot.py | 107 +--- test/line/test_line_plot.py | 566 +++++------------- test/mpr_plot/mpr_plot_custom.yaml | 2 +- test/mpr_plot/test_mpr_plot.py | 38 +- .../custom_reliability_diagram.yaml | 6 +- .../custom_reliability_points1.yaml | 8 +- .../custom_reliability_use_defaults.yaml | 187 ------ test/reliability_diagram/reliability.points1 | 12 - .../test_reliability_diagram.py | 66 +- test/revision_box/custom2_revision_box.yaml | 136 ----- test/revision_box/custom_revision_box.yaml | 7 +- test/revision_box/test_revision_box.py | 63 +- .../custom2_revision_series.yaml | 131 ---- .../custom_revision_series.yaml | 7 +- test/revision_series/test_revision_series.py | 64 +- test/roc_diagram/CTC_ROC.yaml | 4 +- test/roc_diagram/CTC_ROC_ee.yaml | 5 +- test/roc_diagram/CTC_ROC_summary.yaml | 11 +- test/roc_diagram/CTC_ROC_thresh.yaml | 5 +- test/roc_diagram/CTC_ROC_thresh_dump_pts.yaml | 8 +- .../CTC_ROC_thresh_reverse_pts.yaml | 7 +- test/roc_diagram/CTC_wind_reformatted.yaml | 5 +- test/roc_diagram/PCT_ROC.yaml | 5 +- test/roc_diagram/test_roc_diagram.py | 230 ++----- test/roc_diagram/test_roc_diagram.yaml | 8 +- 37 files changed, 429 insertions(+), 1466 deletions(-) delete mode 100644 test/reliability_diagram/custom_reliability_use_defaults.yaml delete mode 100644 test/reliability_diagram/reliability.points1 delete mode 100644 test/revision_box/custom2_revision_box.yaml delete mode 100644 test/revision_series/custom2_revision_series.yaml diff --git a/test/histogram/test_rel_hist.py b/test/histogram/test_rel_hist.py index ad3dfd8e..04ea7083 100644 --- a/test/histogram/test_rel_hist.py +++ b/test/histogram/test_rel_hist.py @@ -7,7 +7,7 @@ @pytest.fixture def setup(module_setup_env): - # Cleanup the plotfile output file from any previous run + # Cleanup the plotfile output file from any previous run cleanup() custom_config_filename = f"{cwd}/rel_hist.yaml" rel_hist.main(custom_config_filename) diff --git a/test/hovmoeller/custom_hovmoeller.yaml b/test/hovmoeller/custom_hovmoeller.yaml index 559cdb46..4a3917e5 100644 --- a/test/hovmoeller/custom_hovmoeller.yaml +++ b/test/hovmoeller/custom_hovmoeller.yaml @@ -1,4 +1,4 @@ -plot_filename: ./hovmoeller_custom_plot.png +plot_filename: !ENV '${TEST_OUTPUT}/hovmoeller_custom_plot.png' input_data_file: /path/to/WorkingDir/precip.erai.sfc.1p0.2x.2014-2016.nc plot_height: 400 plot_width: 600 diff --git a/test/hovmoeller/minimal_hovmoeller.yaml b/test/hovmoeller/minimal_hovmoeller.yaml index 42283c97..0c1fb333 100644 --- a/test/hovmoeller/minimal_hovmoeller.yaml +++ b/test/hovmoeller/minimal_hovmoeller.yaml @@ -1,4 +1,4 @@ # minimal, use all the settings in the default except specify # input data file path and location of the output plot. input_data_file: /path/to/WorkingDir/precip.erai.sfc.1p0.2x.2014-2016.nc -plot_filename: ./hovmoeller_default_plot.png \ No newline at end of file +plot_filename: !ENV '${TEST_OUTPUT}/hovmoeller_default_plot.png' \ No newline at end of file diff --git a/test/hovmoeller/test_hovmoeller.py b/test/hovmoeller/test_hovmoeller.py index f3ce7bdd..f9e1bf3a 100644 --- a/test/hovmoeller/test_hovmoeller.py +++ b/test/hovmoeller/test_hovmoeller.py @@ -2,10 +2,11 @@ import pytest import metplotpy.plots.hovmoeller.hovmoeller as hov from metplotpy.plots import util -#from metcalcpy.compare_images import CompareImages -def dict_to_yaml(data_dict, - output_yaml = "test_hovmoeller.yaml"): + +cwd = os.path.dirname(__file__) + +def dict_to_yaml(data_dict, output_yaml): """Write dict as yaml config file.""" content = "\n".join(["{k}: {v}".format(k=k,v=v) for k,v in data_dict.items()]) with open(output_yaml, 'w') as f: @@ -13,18 +14,8 @@ def dict_to_yaml(data_dict, return output_yaml -def cleanup(file_to_remove): - try: - path = os.getcwd() - os.remove(os.path.join(path, file_to_remove)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - @pytest.mark.skip() -def test_default_plot_images_match(): +def test_default_plot_images_match(module_setup_env, remove_files): ''' Compare an expected plot with the newly created plot to verify that the plot hasn't @@ -34,33 +25,35 @@ def test_default_plot_images_match(): can sometimes be a different size than the expected (which was generated using the same configuration file and data!) ''' - config_file = os.path.join(os.path.dirname(__file__), "minimal_hovmoeller.yaml") + default_plot = "hovmoeller_default_plot.png" + remove_files(os.environ['TEST_OUTPUT'], [default_plot]) + + config_file = os.path.join(cwd, "minimal_hovmoeller.yaml") hov.main(config_file) - default_plot = "./hovmoeller_default_plot.png" - path = os.getcwd() - plot_file = './hovmoeller_expected_default.png' - actual_file = os.path.join(path, plot_file) - comparison = CompareImages(default_plot, actual_file) - assert comparison.mssim == 1 - # Clean up - cleanup(default_plot) + # default_plot = os.path.join(os.environ['TEST_OUTPUT'], default_plot) + # expected_file = 'hovmoeller_expected_default.png' + # actual_file = os.path.join(cwd, expected_file) + # comparison = CompareImages(default_plot, actual_file) + # assert comparison.mssim == 1 + @pytest.mark.skip("needs large netCDF file to run") -def test_custom_plot_created(): - config_file = os.path.join(os.path.dirname(__file__), "custom_hovmoeller.yaml") +def test_custom_plot_created(module_setup_env, remove_files): + expected_file = "hovmoeller_custom_plot.png" + + remove_files(os.environ['TEST_OUTPUT'], [expected_file]) + + config_file = os.path.join(cwd, "custom_hovmoeller.yaml") hov.main(config_file) - custom_plot = "./hovmoeller_custom_plot.png" - assert os.path.isfile(custom_plot) == True + + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) # This plot should be different from the default-it has different dimensions # so the comparison should raise a ValueError - default_plot = './hovmoeller_expected_default.png' - with pytest.raises(ValueError): - CompareImages(default_plot, custom_plot) - - # Clean up - cleanup(custom_plot) + # default_plot = os.path.join(cwd, 'hovmoeller_expected_default.png') + # with pytest.raises(ValueError): + # CompareImages(default_plot, expected_file) def make_config(nc_file, out_file): @@ -80,12 +73,19 @@ def make_config(nc_file, out_file): } return config -def test_hovmoeller(nc_test_file,assert_json_equal): - out_file = "hovmoeller_test.png" + +def test_hovmoeller(module_setup_env, remove_files, nc_test_file, assert_json_equal, tmp_path_factory): + output_dir = os.environ['TEST_OUTPUT'] + out_file = os.path.join(output_dir, "hovmoeller_test.png") + + remove_files(output_dir, [os.path.basename(out_file)]) + config = make_config(nc_test_file, out_file) # basic test to see if output writes - min_yaml = dict_to_yaml(config) + output_yaml = tmp_path_factory.mktemp("data") / "test_hovmoeller.yaml" + min_yaml = dict_to_yaml(config, output_yaml=str(output_yaml)) + hov.main(min_yaml) assert os.path.isfile(out_file) @@ -93,22 +93,27 @@ def test_hovmoeller(nc_test_file,assert_json_equal): # test actual functions from plot object plot_obj = hov.Hovmoeller(util.get_params(min_yaml)) + # note: initializing Hovmoeller removes out_file that was previously written + + plot_obj.save_to_file() + assert os.path.isfile(out_file) + # check html write out plot_obj.write_html() - out_html = config['plot_filename'].split('.')[0] + '.html' + base_name, _ = os.path.splitext(config['plot_filename']) + out_html = f"{base_name}.html" assert os.path.isfile(out_html) # finally check json plot values # to regenerate json file run: - plot_obj.figure.write_json('hovmoeller_test.json') - assert_json_equal(plot_obj.figure, 'hovmoeller_test.json') + json_output = os.path.join(output_dir, "hovmoeller_test.json") + plot_obj.figure.write_json(json_output) + assert_json_equal(plot_obj.figure, json_output) - # Clean up - cleanup(out_file) - cleanup(out_html) -def test_get_lat_str(nc_test_file): - min_yaml = dict_to_yaml(make_config(nc_test_file, "test.png")) +def test_get_lat_str(module_setup_env, nc_test_file, tmp_path_factory): + output_yaml = tmp_path_factory.mktemp("data") / "test_hovmoeller.yaml" + min_yaml = dict_to_yaml(make_config(nc_test_file, "test.png"), output_yaml) plot_obj = hov.Hovmoeller(util.get_params(min_yaml)) actual = plot_obj.get_lat_str(-4,-2) diff --git a/test/line/custom_line.yaml b/test/line/custom_line.yaml index e38a6d85..3ee84e62 100644 --- a/test/line/custom_line.yaml +++ b/test/line/custom_line.yaml @@ -30,6 +30,7 @@ derived_series_1: derived_series_2: [] dump_points_1: 'False' dump_points_2: 'False' +points_path: !ENV '${TEST_OUTPUT}' event_equal: 'True' fcst_var_val_1: RH: @@ -94,7 +95,7 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/line.png' +plot_filename: !ENV '${TEST_OUTPUT}/line.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -102,11 +103,6 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -#points_path: /dir_to_save_points1_file random_seed: null series_line_style: - '-' diff --git a/test/line/custom_line2.yaml b/test/line/custom_line2.yaml index cafc9708..52c1350b 100644 --- a/test/line/custom_line2.yaml +++ b/test/line/custom_line2.yaml @@ -95,14 +95,14 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/line.png' +plot_filename: !ENV '${TEST_OUTPUT}/line.png' plot_height: 8.5 plot_res: 72 plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' random_seed: null series_line_style: - '-' diff --git a/test/line/custom_line_from_zero.yaml b/test/line/custom_line_from_zero.yaml index 9d320c8c..3536ca20 100644 --- a/test/line/custom_line_from_zero.yaml +++ b/test/line/custom_line_from_zero.yaml @@ -1,3 +1,7 @@ +plot_filename: !ENV '${TEST_OUTPUT}/line_from_zero.png' + +stat_input: !ENV '${TEST_DIR}/line.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -95,7 +99,7 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/line_from_zero.png' + plot_height: 8.5 plot_res: 72 plot_stat: median @@ -155,7 +159,6 @@ show_signif: - 'False' - 'False' start_from_zero: 'True' -stat_input: !ENV '${TEST_DIR}/line.data' sync_yaxes: 'False' title: test title title_align: 0.5 @@ -219,8 +222,8 @@ lines: type: vert_line show_legend: - -True - -True - -True - -True - -True \ No newline at end of file +- True +- True +- True +- True +- True \ No newline at end of file diff --git a/test/line/custom_line_groups.yaml b/test/line/custom_line_groups.yaml index 0eafc444..e04fb9a3 100644 --- a/test/line/custom_line_groups.yaml +++ b/test/line/custom_line_groups.yaml @@ -1,3 +1,10 @@ +plot_filename: !ENV '${TEST_OUTPUT}/line_groups.png' +points_path: !ENV '${TEST_OUTPUT}' +dump_points_1: 'True' +dump_points_2: 'True' + +stat_input: !ENV '${TEST_DIR}/line_groups.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -25,8 +32,6 @@ derived_series_1: - Group_y1_2 TMP ME - DIFF derived_series_2: [] -dump_points_1: 'True' -dump_points_2: 'True' eqbound_high: 0.001 eqbound_low: -0.001 event_equal: 'False' @@ -87,7 +92,6 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/line_groups.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -125,7 +129,6 @@ show_signif: - 'False' - 'False' - 'False' -stat_input: !ENV '${TEST_DIR}/line_groups.data' sync_yaxes: 'False' title: test title title_align: 0.5 @@ -178,6 +181,6 @@ ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 show_legend: - -True - -True - -True \ No newline at end of file +- True +- True +- True \ No newline at end of file diff --git a/test/line/custom_line_groups2.yaml b/test/line/custom_line_groups2.yaml index 0eedd0cf..6fdcd052 100644 --- a/test/line/custom_line_groups2.yaml +++ b/test/line/custom_line_groups2.yaml @@ -1,3 +1,10 @@ +plot_filename: !ENV '${TEST_OUTPUT}/line_groups2.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' +dump_points_1: 'True' +dump_points_2: 'True' + +stat_input: !ENV '${TEST_DIR}/line_groups.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -33,8 +40,6 @@ derived_series_1: - Group_y1_2 TMP ME - DIFF derived_series_2: [] -dump_points_1: 'True' -dump_points_2: 'True' eqbound_high: 0.001 eqbound_low: -0.001 event_equal: 'False' @@ -92,14 +97,13 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/intermed_files/line_groups.png' + plot_height: 8.5 plot_res: 72 plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' random_seed: null series_line_style: - '-' @@ -131,7 +135,6 @@ show_signif: - 'False' - 'False' - 'False' -stat_input: !ENV '${TEST_DIR}/line_groups.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/line/env_fcst_var.yaml b/test/line/env_fcst_var.yaml index 419c53d7..2e926464 100644 --- a/test/line/env_fcst_var.yaml +++ b/test/line/env_fcst_var.yaml @@ -1,3 +1,10 @@ +plot_filename: !ENV '${TEST_OUTPUT}/env_fcst_var_stat_line.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files/env_fcst_var_stat' +dump_points_1: 'True' +dump_points_2: 'True' + +stat_input: !ENV '${TEST_DIR}/line.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -28,8 +35,6 @@ derived_series_1: - GTS RH MAE - DIFF derived_series_2: [] -dump_points_1: 'True' -dump_points_2: 'True' event_equal: 'True' fcst_var_val_1: !ENV ${FCST_VAR_VAL1}: @@ -94,7 +99,6 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/env_fcst_var_stat_line.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -102,11 +106,6 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -points_path: !ENV '${TEST_DIR}/intermed_files/env_fcst_var_stat' random_seed: null series_line_style: - '-' @@ -153,7 +152,6 @@ show_signif: - 'False' - 'False' - 'False' -stat_input: !ENV '${TEST_DIR}/line.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/line/fbias_fixed_vars_vals.yaml b/test/line/fbias_fixed_vars_vals.yaml index 75005942..d0a468f2 100644 --- a/test/line/fbias_fixed_vars_vals.yaml +++ b/test/line/fbias_fixed_vars_vals.yaml @@ -1,3 +1,7 @@ +plot_filename: !ENV '${TEST_OUTPUT}/fbias_fixed_vars.png' +points_path: !ENV '${TEST_OUTPUT}' +stat_input: !ENV '${TEST_DIR}/fbias_data.txt' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -166,7 +170,6 @@ plot_ci: plot_disp: - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/fbias_fixed_vars.png' plot_height: 8.5 plot_res: 72 plot_stat: mean @@ -175,13 +178,6 @@ plot_units: in plot_width: 11.0 start_from_zero: True - -#Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -points_path: !ENV '${TEST_DIR}/' - random_seed: null series_line_style: - '-' @@ -207,7 +203,6 @@ show_nstats: 'False' show_signif: - 'False' - 'False' -stat_input: !ENV '${TEST_DIR}/fbias_data.txt' sync_yaxes: 'False' title: "Fixed variable fcst_thresh >0.0 for FBIAS" title_align: 0.5 @@ -258,5 +253,5 @@ ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 show_legend: - -True - -True +- True +- True diff --git a/test/line/mv_custom_vert_line.yaml b/test/line/mv_custom_vert_line.yaml index d26335f0..49a4183a 100644 --- a/test/line/mv_custom_vert_line.yaml +++ b/test/line/mv_custom_vert_line.yaml @@ -1,3 +1,7 @@ +points_path: !ENV '${TEST_OUTPUT}/intermed_files' +plot_filename: !ENV '${TEST_OUTPUT}/vert_line_plot.png' +stat_input: !ENV '${TEST_DIR}/vert_line_plot_data.txt' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -102,7 +106,6 @@ plot_ci: plot_disp: - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/vert_line_plot.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -110,11 +113,7 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -points_path: !ENV '${TEST_DIR}/intermed_files' + random_seed: null series_line_style: - '-' @@ -140,7 +139,6 @@ show_nstats: 'True' show_signif: - 'True' - 'True' -stat_input: !ENV '${TEST_DIR}/vert_line_plot_data.txt' sync_yaxes: 'False' title: test title title_align: 0.5 @@ -191,5 +189,5 @@ ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 show_legend: - -True - -True +- True +- True diff --git a/test/line/test_line_groups_plot.py b/test/line/test_line_groups_plot.py index a0756c03..ec55d375 100644 --- a/test/line/test_line_groups_plot.py +++ b/test/line/test_line_groups_plot.py @@ -1,99 +1,38 @@ import pytest import os from metplotpy.plots.line import line as l -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -@pytest.fixture -def setup(): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line_groups.yaml" - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - l.main(custom_config_filename) +def test_custom_line_groups(module_setup_env, remove_files): + """Checking that the plot and data files are getting created""" + expected_files = ( + "line_groups.png", + "line_groups.points1", + "line_groups.points2", + "line_groups.html", + ) + remove_files(os.environ['TEST_OUTPUT'], expected_files) -def cleanup(): - # remove the line.png and .points files - # from any previous runs - try: - plot_file = 'line_groups.png' - points_file_1 = 'line_groups.points1' - points_file_2 = 'line_groups.points2' - html_file = 'line_groups.html' - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(cwd, points_file_1)) - os.remove(os.path.join(cwd, points_file_2)) - os.remove(os.path.join(cwd, html_file)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass + l.main(f"{cwd}/custom_line_groups.yaml") + for expected_file in expected_files: + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) -@pytest.mark.parametrize("test_input,expected", - ([f"{cwd}/line_groups.png", True], [f"{cwd}/line_groups.points1", True])) -def test_files_exist(setup, test_input, expected): - ''' - Checking that the plot and data files are getting created - ''' - assert os.path.isfile(test_input) == expected - cleanup() -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - plot_file = 'line_groups.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/line_groups_expected.png', actual_file) - assert comparison.mssim == 1 - cleanup() +def test_custom_line_groups2(module_setup_env, remove_files): + """Checking that the plot and data files are getting created""" + expected_files = ( + "line_groups2.png", + "intermed_files/line_groups.points1", + "intermed_files/line_groups.points2", + ) -@pytest.mark.parametrize("test_input,expected", - ([f"{cwd}/intermed_files/line_groups.png", True], [f"{cwd}/intermed_files/line_groups.points1", True])) -def test_files_exist( test_input, expected): - ''' - Checking that the plot and data files are getting created - ''' - intermed_dir = f'{cwd}/intermed_files' - try: - os.mkdir(intermed_dir) - except FileExistsError as e: - pass + remove_files(os.environ['TEST_OUTPUT'], expected_files) - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line_groups2.yaml" + l.main(f"{cwd}/custom_line_groups2.yaml") - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - l.main(custom_config_filename) - assert os.path.isfile(test_input) == expected - - # remove the files that were created, cleanup() isn't applicable for this test. - try: - subdir = os.path.join(cwd, intermed_dir) - plot_file = 'line_groups.png' - points_file_1 = 'line_groups.points1' - points_file_2 = 'line_groups.points2' - html_file = 'line_groups.html' - os.remove(os.path.join(subdir, plot_file)) - os.remove(os.path.join(subdir, points_file_1)) - os.remove(os.path.join(subdir, points_file_2)) - os.remove(os.path.join(subdir, html_file)) - os.rmdir(intermed_dir) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass + for expected_file in expected_files: + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) diff --git a/test/line/test_line_plot.py b/test/line/test_line_plot.py index c7830ec8..165a62e4 100644 --- a/test/line/test_line_plot.py +++ b/test/line/test_line_plot.py @@ -5,441 +5,203 @@ cwd = os.path.dirname(__file__) -# from metcalcpy.compare_images import CompareImages - - -@pytest.fixture -def setup(): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line.yaml" - - # Invoke the command to generate a line plot based on - # the custom config file. - l.main(custom_config_filename) - - -def cleanup(): - # remove the line.png and .points files - # from any previous runs - try: - plot_file = 'line.png' - points_file_1 = 'line.points1' - points_file_2 = 'line.points2' - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(cwd, points_file_1)) - os.remove(os.path.join(cwd, points_file_2)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - -@pytest.mark.parametrize("test_input,expected", - ([f"{cwd}/line.png", True], [f"{cwd}/line.points1", False], - [f"{cwd}/line.points2", False])) -def test_files_exist(setup, test_input, expected): - ''' - Checking that the plot file is getting created but the - .points1 and .points2 files are NOT - ''' - assert os.path.isfile(test_input) == expected - cleanup() - - -@pytest.mark.parametrize("test_input,expected", - ([f"{cwd}/line.png", True], [f"{cwd}/intermed_files/line.points1", True], - [f"{cwd}/intermed_files/line.points2", True])) -def test_points_files_exist(test_input, expected): - ''' - Checking that the plot and point data files are getting created - ''' - - # create the intermediate directory to store the .points1 and .points2 files - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError as e: - pass - - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line2.yaml" - l.main(custom_config_filename) - - # Test for expected values - assert os.path.isfile(test_input) == expected - - # cleanup intermediate files and plot - try: - plot_file = 'line.png' - points_file_1 = 'line.points1' - points_file_2 = 'line.points2' - intermed_path = os.path.join(cwd, 'intermed_files') - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(intermed_path, points_file_1)) - os.remove(os.path.join(intermed_path, points_file_2)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - -def test_no_nans_in_points_files(): - ''' - Checking that the point data files do not have any NaN's - ''' - - # create the intermediate directory to store the .points1 and .points2 files - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError as e: - pass - - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line2.yaml" - l.main(custom_config_filename) + +def test_custom_line_without_points(module_setup_env, remove_files): + """Checking that the plot file is getting created but the .points1 and .points2 files are NOT""" + output_dir = os.environ['TEST_OUTPUT'] + expected_files = ['line.png'] + not_expected_files = ['line.points1', 'line.points2'] + + remove_files(os.environ['TEST_OUTPUT'], expected_files + not_expected_files) + + l.main(f"{cwd}/custom_line.yaml") + + for expected_file in expected_files: + assert os.path.isfile(os.path.join(output_dir, expected_file)) + + for not_expected_file in not_expected_files: + assert not os.path.isfile(os.path.join(output_dir, not_expected_file)) + + +def test_custom_line2_with_points(module_setup_env, remove_files): + """Checking that the plot and point data files are getting created. + Checking that no NaNs are found in the pointsN files""" + output_dir = os.environ['TEST_OUTPUT'] + expected_files = ( + 'line.png', + 'intermed_files/line.points1', + 'intermed_files/line.points2' + ) + + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + l.main(f"{cwd}/custom_line2.yaml") + + for expected_file in expected_files: + assert os.path.isfile(os.path.join(output_dir, expected_file)) # Check for NaN's in the intermediate files,_line.points1 and line.points2 # Fail if there are any NaN's-this indicates something went wrong with the - # line_series.py module's _create_series_points() method. - nans_found = False - with open(f"{cwd}/intermed_files/line.points1", "r") as f: + # line_series.py module's _create_series_points() method. + with open(f"{output_dir}/intermed_files/line.points1", "r") as f: data = f.read() - if "NaN" in data: - nans_found = True - - assert nans_found == False + assert "NaN" not in data - # Now check line.points2 - with open(f"{cwd}/intermed_files/line.points2", "r") as f: + with open(f"{output_dir}/intermed_files/line.points2", "r") as f: data = f.read() - if "NaN" in data: - nans_found = True - - assert nans_found == False + assert "NaN" not in data # Verify that the nan.points1 file does indeed trigger a "nans_found" with open(f"{cwd}/nan.points1", "r") as f: data = f.read() - if "NaN" in data: - nans_found = True - - # assert - assert nans_found == True - - # cleanup intermediate files and plot - try: - plot_file = 'line.png' - points_file_1 = 'line.points1' - points_file_2 = 'line.points2' - intermed_path = os.path.join(cwd, 'intermed_files') - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(intermed_path, points_file_1)) - os.remove(os.path.join(intermed_path, points_file_2)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - -@pytest.mark.skip() -def test_images_match(setup): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - plot_file = './line.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/line_expected.png', actual_file) - - # !!!WARNING!!! SOMETIMES FILE SIZES DIFFER IN SPITE OF THE PLOTS LOOKING THE SAME - # THIS TEST IS NOT 100% RELIABLE because of differences in machines, OS, etc. - assert comparison.mssim == 1 - cleanup() - - -@pytest.mark.skip() -def test_new_images_match(): - ''' - Compare an expected plot with the start_at_zero option, with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/custom_line_from_zero.yaml" - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - l.main(custom_config_filename) - plot_file = 'line_from_zero.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/line_expected_from_zero.png', actual_file) - - # !!!WARNING!!! SOMETIMES FILE SIZES DIFFER IN SPITE OF THE PLOTS LOOKING THE SAME - # THIS TEST IS NOT 100% RELIABLE because of differences in machines, OS, etc. - assert comparison.mssim == 1 - - # cleanup plot - try: - plot_file = 'line_from_zero.png' - os.remove(os.path.join(cwd, plot_file)) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - -def test_vertical_plot(): - ''' - - Test that the y1 values from the Python version of the vertical plot + assert "NaN" in data + + +def test_custom_line_from_zero(module_setup_env, remove_files): + """Compare an expected plot with the start_at_zero option, with the + newly created plot to verify that the plot hasn't changed in appearance.""" + expected_files = [ + 'line_from_zero.png' + ] + + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + l.main(f"{cwd}/custom_line_from_zero.yaml") + + assert(os.path.isfile(f"{os.environ['TEST_OUTPUT']}/line_from_zero.png")) + + +def test_vertical_plot(module_setup_env, remove_files): + """Test that the y1 values from the Python version of the vertical plot match the y1 values from the METviewer Rplot version of the vertical plot. - Avoid relying on image comparison tests. - - ''' - - # create the intermediate directory to store the .points1 and .points2 files - try: - os.mkdir(os.path.join(os.getcwd(), 'intermed_files')) - except FileExistsError as e: - pass - - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIRR'] = cwd - custom_config_filename = f"{cwd}/mv_custom_vert_line.yaml" - l.main(custom_config_filename) - - try: - plot_file = 'vert_line_plot.png' - points_file_1 = 'vert_line_plot.points1' - points_file_2 = 'vert_line_plot.points2' - intermed_path = os.path.join(cwd, 'intermed_files') - - # Retrieve the .points1 files generated by METviewer and METplotpy respectively - mv_df = pd.read_csv(f'{cwd}/intermed_files/vert_plot_y1_from_metviewer.points1', - sep=" ", header=None) - mpp_df = pd.read_csv(f'{cwd}/intermed_files/vert_line_plot.points1', sep=" ", - header=None) - - # ----------------------- - # Compare various values - # ----------------------- - # Verify we are comparing data frames created from the same data - # Same number of rows: - assert mv_df.shape[0] == mpp_df.shape[0] - # Same number of columns: - assert mv_df.shape[1] == mpp_df.shape[1] - - # Values for each column are the same (accounting for precision between R and - # Python and - # different host machines) - for col in range(mv_df.shape[1]): - mv_col: pd.Series = mv_df.loc[:, col] - mpp_col: pd.Series = mpp_df.loc[:, col] - col_diff: pd.Series = mv_col - mpp_col - sum_diff: float = abs(col_diff.sum()) - - # Allow for differences in arithmetic between machines and differences in - # R vs Python arithmetic - # allow difference out to 5th significant figure. - assert sum_diff < 0.00001 - - # cleanup intermediate files and plot - os.remove(os.path.join(path, plot_file)) - os.remove(os.path.join(intermed_path, points_file_1)) - os.remove(os.path.join(intermed_path, points_file_2)) - os.remove(f'{cwd}/intermed_files/vert_plot_y1_from_metviewer.points1') - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - - -def test_fixed_var_val(): - """ - Verify that the fixed_vars_vals_input setting reproduces the - same data points that METviewer produces. - - """ - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/fbias_fixed_vars_vals.yaml" - - # Invoke the command to generate a line plot based on - # the custom config file. - l.main(custom_config_filename) - - expected_points = f"{cwd}/intermed_files/mv_fixed_var_vals.points1" - - try: - plot_file = 'fbias_fixed_vars_reformatted_input.png' - - intermed_path = os.path.join(cwd, 'intermed_files') - - # Retrieve the .points1 files generated by METviewer and METplotpy respectively - mv_df = pd.read_csv(f'{cwd}/intermed_files/mv_fixed_var_vals.points1', - sep="\t", header=None) - mpp_df = pd.read_csv(f'{cwd}/fbias.points1', sep="\t", header=None) - - # Verify that the values in the generated points1 file are identical - # to those in the METviewer points1 file. - - # First, verify that the points1 files have the same shape - num_mv_rows = mv_df.shape[0] - num_mv_cols = mv_df.shape[1] - num_mpp_rows = mpp_df.shape[0] - num_mpp_cols = mpp_df.shape[1] - - assert num_mv_rows == num_mpp_rows - assert num_mv_cols == num_mpp_cols - - # Verify that all of the rows of both have identical values - for i in range(num_mv_rows): - assert mv_df.iloc[i][0] == mpp_df.iloc[i][0] - - # Clean up the fbias.points1, fbias.points2, and .png files - os.remove(f'{cwd}/fbias.points1') - os.remove(f'{cwd}/fbias.points2') - os.remove(f'{cwd}/fbias_fixed_vars_reformatted_input.png') - os.remove(f'{cwd}/intermed_files/mv_fixed_var_vals.points1') - os.remove(expected_points) - os.rmdir(f'{cwd}/intermed_files') - - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass - -def test_envs_fcst_var_stat(): - """ - Verify that the environment vars used for fcst_var_val1/2 and stat1/2 in - a config file creates the same data for plotting as a config file with the - fcst_var_val1 and stat hard-coded in a config file. - - """ - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/env_fcst_var.yaml" - - # Invoke the command to generate a line plot based on - # the custom config file. - os.environ['FCST_VAR_VAL1'] = "RH" - os.environ['FCST_VAR_STAT1'] = 'MAE' - os.environ['FCST_VAR_VAL2'] = "TMP" - os.environ['FCST_VAR_STAT2'] = 'ME' + Avoid relying on image comparison tests.""" + expected_files = [ + 'vert_line_plot.png', + 'intermed_files/vert_line_plot.points1' + ] - l.main(custom_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) - expected_points1 = f"{cwd}/intermed_files/expected_var_stat_line.points1" - expected_points2 = f"{cwd}/intermed_files/expected_var_stat_line.points2" - expected_df1 = pd.read_csv(f'{expected_points1}', - sep="\t", header=None) - expected_df2 = pd.read_csv( - f'{expected_points2}', sep="\t", header=None - ) - num_expected1_rows= expected_df1.shape[0] - num_expected1_cols = expected_df1.shape[1] - num_expected2_rows = expected_df2.shape[0] - num_expected2_cols = expected_df2.shape[1] + l.main(f"{cwd}/mv_custom_vert_line.yaml") + + for expected_file in expected_files: + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) + + output_dir = os.environ['TEST_OUTPUT'] + # Retrieve the .points1 files generated by METviewer and METplotpy respectively + mv_df = pd.read_csv(f'{cwd}/intermed_files/vert_plot_y1_from_metviewer.points1', + sep=" ", header=None) + mpp_df = pd.read_csv(f'{output_dir}/intermed_files/vert_line_plot.points1', sep=" ", + header=None) - try: + # ----------------------- + # Compare various values + # ----------------------- + # Verify we are comparing data frames created from the same data + # Same number of rows: + assert mv_df.shape[0] == mpp_df.shape[0] + # Same number of columns: + assert mv_df.shape[1] == mpp_df.shape[1] - # Retrieve the .points1 files generated by METplotpy respectively - mpp_df1 = pd.read_csv(f'{cwd}/intermed_files/env_fcst_var_stat/line.points1', - sep="\t", header=None) - mpp_df2 = pd.read_csv( - f'{cwd}/intermed_files/env_fcst_var_stat/line.points2', sep="\t", header=None - ) + # Values for each column are the same (accounting for precision between R and + # Python and + # different host machines) + for col in range(mv_df.shape[1]): + mv_col: pd.Series = mv_df.loc[:, col] + mpp_col: pd.Series = mpp_df.loc[:, col] + col_diff: pd.Series = mv_col - mpp_col + sum_diff: float = abs(col_diff.sum()) - # Verify that the values in the generated points1/2 files are identical - # to those in the rh_mae_tmp_me_line.points1/2 files. + # Allow for differences in arithmetic between machines and differences in + # R vs Python arithmetic + # allow difference out to 5th significant figure. + assert sum_diff < 0.00001 - # First, verify that the points1 files have the same shape - num_mpp1_rows = mpp_df1.shape[0] - num_mpp1_cols = mpp_df1.shape[1] - num_mpp2_rows = mpp_df2.shape[0] - num_mpp2_cols = mpp_df2.shape[1] - assert num_expected1_rows == num_mpp1_rows - assert num_expected1_cols == num_mpp1_cols - assert num_expected2_rows == num_mpp2_rows - assert num_expected2_cols == num_mpp2_cols +def test_fbias_fixed_vars_vals(module_setup_env, remove_files): + """Verify that the fixed_vars_vals_input setting reproduces the same data points that METviewer produces.""" + expected_files = ( + 'fbias_fixed_vars.png', + 'fbias.points1', + ) - assert mpp_df1.equals(expected_df1) - assert mpp_df2.equals(expected_df2) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + l.main(f"{cwd}/fbias_fixed_vars_vals.yaml") + for expected_file in expected_files: + assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) - # Clean up the fbias.points1, fbias.points2, and .png files - os.remove(f'{cwd}/intermed_files/env_fcst_var_stat/line.points1') - os.remove(f'{cwd}/intermed_files/env_fcst_var_stat/line.points2') - os.rmdir(f'{cwd}/intermed_files/env_fcst_var_stat') + # Retrieve the .points1 files generated by METviewer and METplotpy respectively + mv_df = pd.read_csv(f'{cwd}/intermed_files/mv_fixed_var_vals.points1', + sep="\t", header=None) + mpp_df = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/fbias.points1', sep="\t", header=None) - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass + # Verify that the values in the generated points1 file are identical + # to those in the METviewer points1 file. + # First, verify that the points1 files have the same shape + num_mv_rows = mv_df.shape[0] + num_mv_cols = mv_df.shape[1] + num_mpp_rows = mpp_df.shape[0] + num_mpp_cols = mpp_df.shape[1] -@pytest.mark.skip("Image comparison for development only due to differences in hosts") -def test_fixed_var_val_image_compare(): - """ - Verify that the fixed_vars_vals_input setting reproduces the - expected plot. + assert num_mv_rows == num_mpp_rows + assert num_mv_cols == num_mpp_cols - """ - from metcalcpy.compare_images import CompareImages + # Verify that all of the rows of both have identical values + for i in range(num_mv_rows): + assert mv_df.iloc[i][0] == mpp_df.iloc[i][0] - # Set up the METPLOTPY_BASE so that met_plot.py will correctly find - # the config directory containing all the default config files. - os.environ['METPLOTPY_BASE'] = f"{cwd}/../../" - os.environ['TEST_DIR'] = cwd - custom_config_filename = f"{cwd}/fbias_fixed_vars_vals.yaml" - # Invoke the command to generate a line plot based on - # the custom config file. - l.main(custom_config_filename) +def test_envs_fcst_var_stat(module_setup_env, remove_files): + """Verify that the environment vars used for fcst_var_val1/2 and stat1/2 in + a config file creates the same data for plotting as a config file with the + fcst_var_val1 and stat hard-coded in a config file.""" - expected_plot = f"{cwd}/expected_fbias_fixed_vars.png" + expected_files = ( + 'env_fcst_var_stat_line.png', + 'intermed_files/env_fcst_var_stat/line.points1', + 'intermed_files/env_fcst_var_stat/line.points2', + ) - try: - plot_file = 'fbias_fixed_vars.png' - created_file = os.path.join(cwd, plot_file) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + os.environ['FCST_VAR_VAL1'] = "RH" + os.environ['FCST_VAR_STAT1'] = 'MAE' + os.environ['FCST_VAR_VAL2'] = "TMP" + os.environ['FCST_VAR_STAT2'] = 'ME' + + l.main(f"{cwd}/env_fcst_var.yaml") + + expected_points1 = f"{cwd}/intermed_files/expected_var_stat_line.points1" + expected_points2 = f"{cwd}/intermed_files/expected_var_stat_line.points2" + expected_df1 = pd.read_csv(f'{expected_points1}', sep="\t", header=None) + expected_df2 = pd.read_csv(f'{expected_points2}', sep="\t", header=None) + num_expected1_rows = expected_df1.shape[0] + num_expected1_cols = expected_df1.shape[1] + num_expected2_rows = expected_df2.shape[0] + num_expected2_cols = expected_df2.shape[1] - # first verify that the output plot was created - if os.path.exists(created_file): - assert True - else: - assert False + # Retrieve the .points1 files generated by METplotpy respectively + mpp_df1 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/intermed_files/env_fcst_var_stat/line.points1', + sep="\t", header=None) + mpp_df2 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/intermed_files/env_fcst_var_stat/line.points2', sep="\t", header=None) - comparison = CompareImages(expected_plot, created_file) + # Verify that the values in the generated points1/2 files are identical + # to those in the rh_mae_tmp_me_line.points1/2 files. - # !!!WARNING!!! SOMETIMES FILE SIZES DIFFER IN SPITE OF THE PLOTS LOOKING THE - # SAME - # THIS TEST IS NOT 100% RELIABLE because of differences in machines, OS, etc. - assert comparison.mssim == 1 + # First, verify that the points1 files have the same shape + num_mpp1_rows = mpp_df1.shape[0] + num_mpp1_cols = mpp_df1.shape[1] + num_mpp2_rows = mpp_df2.shape[0] + num_mpp2_cols = mpp_df2.shape[1] - # Clean up the fbias.points1, fbias.points2 and the png files - os.remove(f'{cwd}/fbias.points1') - os.remove(f'{cwd}/fbias.points2') - os.remove(created_file) + assert num_expected1_rows == num_mpp1_rows + assert num_expected1_cols == num_mpp1_cols + assert num_expected2_rows == num_mpp2_rows + assert num_expected2_cols == num_mpp2_cols - except OSError as e: - # Typically when files have already been removed or - # don't exist. Ignore. - pass + assert mpp_df1.equals(expected_df1) + assert mpp_df2.equals(expected_df2) diff --git a/test/mpr_plot/mpr_plot_custom.yaml b/test/mpr_plot/mpr_plot_custom.yaml index aad4b441..9ba67457 100644 --- a/test/mpr_plot/mpr_plot_custom.yaml +++ b/test/mpr_plot/mpr_plot_custom.yaml @@ -1,5 +1,5 @@ wind_rose: True -plot_filename: !ENV '${TEST_DIR}/mpr_plots.png' +plot_filename: !ENV '${TEST_OUTPUT}/mpr_plots.png' wind_rose_breaks: - 0.0 - 1.0 diff --git a/test/mpr_plot/test_mpr_plot.py b/test/mpr_plot/test_mpr_plot.py index ee724505..5076af05 100644 --- a/test/mpr_plot/test_mpr_plot.py +++ b/test/mpr_plot/test_mpr_plot.py @@ -1,41 +1,15 @@ import pytest import os from metplotpy.plots.mpr_plot import mpr_plot -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['mpr_plots.png'] +def test_files_exist(module_setup_env, remove_files): + """Checking that the plot and data files are getting created""" + expected_file = "mpr_plots.png" -@pytest.fixture -def setup(setup_env, remove_files): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/mpr_plot_custom.yaml" + remove_files(os.environ['TEST_OUTPUT'], expected_file) - # Invoke the command to generate a Performance Diagram based on - # the custom_performance_diagram.yaml custom config file. - mpr_plot.main(custom_config_filename) + mpr_plot.main(f"{cwd}/mpr_plot_custom.yaml") - -@pytest.mark.parametrize("test_input, expected", - (["mpr_plots.png", True], ["mpr_plots_expected.png", True])) -def test_files_exist(setup, test_input, expected, remove_files): - """ - Checking that the plot and data files are getting created - """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("unreliable-sometimes fails due to differences between machines.") -def test_images_match(setup, remove_files): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/mpr_plots_expected.png', f'{cwd}/mpr_plots.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/reliability_diagram/custom_reliability_diagram.yaml b/test/reliability_diagram/custom_reliability_diagram.yaml index 30c8e6bb..e931394f 100644 --- a/test/reliability_diagram/custom_reliability_diagram.yaml +++ b/test/reliability_diagram/custom_reliability_diagram.yaml @@ -1,3 +1,7 @@ +plot_filename: !ENV '${TEST_OUTPUT}/custom_reliability_diagram.png' + +stat_input: !ENV '${TEST_DIR}/plot_20210311_145053.data' + add_noskill_line: 'True' add_reference_line: 'True' add_skill_line: 'True' @@ -81,7 +85,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/custom_reliability_diagram.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -111,7 +114,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/plot_20210311_145053.data' summary_curves: [] sync_yaxes: 'False' title: test title diff --git a/test/reliability_diagram/custom_reliability_points1.yaml b/test/reliability_diagram/custom_reliability_points1.yaml index adcbb785..8ea99682 100644 --- a/test/reliability_diagram/custom_reliability_points1.yaml +++ b/test/reliability_diagram/custom_reliability_points1.yaml @@ -1,3 +1,8 @@ +plot_filename: !ENV '${TEST_OUTPUT}/reliability_points1.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + +stat_input: !ENV '${TEST_DIR}/reliability.data' + add_noskill_line: 'True' add_reference_line: 'True' add_skill_line: 'True' @@ -96,7 +101,6 @@ plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' random_seed: null rely_event_hist: 'True' series_line_style: @@ -180,8 +184,6 @@ ytlab_horiz: 0.5 ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 -plot_filename: !ENV '${TEST_DIR}/intermed_files/reliability.png' -stat_input: !ENV '${TEST_DIR}/reliability.data' noskill_line_col: 'green' reference_line_col: 'blue' show_legend: diff --git a/test/reliability_diagram/custom_reliability_use_defaults.yaml b/test/reliability_diagram/custom_reliability_use_defaults.yaml deleted file mode 100644 index b2a2d711..00000000 --- a/test/reliability_diagram/custom_reliability_use_defaults.yaml +++ /dev/null @@ -1,187 +0,0 @@ -add_noskill_line: 'True' -add_reference_line: 'True' -add_skill_line: 'True' -alpha: 0.05 -box_avg: 'False' -box_boxwex: 0.2 -box_notch: 'False' -box_outline: 'True' -box_pts: 'False' -caption_align: 0.0 -caption_col: '#ff1493' -caption_offset: 3.0 -caption_size: 1.0 -caption_weight: 1 -cex: 1 -colors: -- '#ff0000' -- '#00ff7f' -- '#8000ff' -con_series: -- 1 -- 1 -- 1 -create_html: 'False' -derived_series_1: [] -derived_series_2: [] -dump_points_1: 'True' -dump_points_2: 'False' -event_equal: 'True' -fcst_var_val_1: - APCP_03_ENS_FREQ_ge12.7: - - PSTD_CALIBRATION - - PSTD_BASER - - PSTD_NI -fcst_var_val_2: null -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: [] -indy_stagger_1: 'True' -indy_stagger_2: 'False' -indy_vals: -- '0' -- '0.1' -- '0.2' -- '0.3' -- '0.4' -- '0.5' -- '0.6' -- '0.7' -- '0.8' -- '0.9' -indy_var: thresh_i -inset_hist: 'True' -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: pct -list_stat_1: -- PSTD_CALIBRATION -- PSTD_BASER -- PSTD_NI -list_stat_2: [] -list_static_val: - fcst_var: APCP_03_ENS_FREQ_ge12.7 -mar: -- 8 -- 4 -- 5 -- 4 -method: perc -mgp: -- 1 -- 1 -- 0 -num_iterations: 1000 -num_threads: -1 -plot_caption: capcap uuu -plot_ci: -- boot -- boot -- none -plot_disp: -- 'True' -- 'True' -- 'True' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -random_seed: null -rely_event_hist: 'True' -series_line_style: -- '-' -- '-' -- '-' -series_line_width: -- 1 -- 1 -- 1 -series_order: -- 1 -- 2 -- 3 -series_symbols: -- . -- . -- . -series_type: -- b -- b -- b -series_val_1: - model: - - rap0_3_spptens - - rap0_7_spptens -series_val_2: {} -show_nstats: 'False' -show_signif: -- 'False' -- 'False' -- 'False' -summary_curves: -- mean -sync_yaxes: 'False' -title: The
test -title_align: 0.5 -title_offset: -2 -title_size: 1.6 -title_weight: 4.0 -user_legend: [] -variance_inflation_factor: 'False' -vert_plot: 'False' -x2lab_align: 0.5 -x2lab_offset: -0.5 -x2lab_size: 0.8 -x2lab_weight: 1 -x2tlab_horiz: 0.5 -x2tlab_orient: 1 -x2tlab_perp: 1 -x2tlab_size: 0.8 -xaxis: some x -xaxis_reverse: 'False' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 2 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -y2lab_align: 0.5 -y2lab_offset: 1 -y2lab_size: 1 -y2lab_weight: 1 -y2lim: [] -y2tlab_horiz: 0.5 -y2tlab_orient: 1 -y2tlab_perp: 1 -y2tlab_size: 1.0 -yaxis_1: some y -yaxis_2: '' -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 2 -ylab_weight: 3 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 -plot_filename: !ENV '${TEST_DIR}/reliability.png' -stat_input: !ENV '${TEST_DIR}/reliability.data' -show_legend: -- 'True' -- 'True' -- 'True' diff --git a/test/reliability_diagram/reliability.points1 b/test/reliability_diagram/reliability.points1 deleted file mode 100644 index bf85025d..00000000 --- a/test/reliability_diagram/reliability.points1 +++ /dev/null @@ -1,12 +0,0 @@ -0.125000 0.005442 0.005189 0.005679 -0.375000 0.095076 0.085846 0.105048 -0.625000 0.155515 0.140058 0.172051 -0.875000 0.254195 0.225073 0.283987 -0.125000 0.005559 0.005308 0.005825 -0.375000 0.121477 0.109670 0.133229 -0.625000 0.180538 0.161182 0.202423 -0.875000 0.306996 0.270081 0.342544 -0.125000 0.005501 0.005249 0.005752 -0.375000 0.108276 0.097758 0.119138 -0.625000 0.168027 0.150620 0.187237 -0.875000 0.280596 0.247577 0.313265 diff --git a/test/reliability_diagram/test_reliability_diagram.py b/test/reliability_diagram/test_reliability_diagram.py index 0ceeb222..7aa6315e 100644 --- a/test/reliability_diagram/test_reliability_diagram.py +++ b/test/reliability_diagram/test_reliability_diagram.py @@ -1,65 +1,19 @@ - import pytest import os from metplotpy.plots.reliability_diagram import reliability as r -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['reliability.png', 'reliability.points1'] - - -@pytest.fixture -def setup(setup_env, remove_files): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_reliability_use_defaults.yaml" - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - r.main(custom_config_filename) - - -@pytest.mark.parametrize("test_input,expected", - ([CLEANUP_FILES[0], True], [CLEANUP_FILES[1], True])) -def test_files_exist(setup, test_input, expected, remove_files): - ''' - Checking that the plot and data files are getting created - ''' - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("depends on machine on which this is run") -def test_images_match(setup, remove_files): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - comparison = CompareImages(f'{cwd}/{CLEANUP_FILES[0]}', f'{cwd}/{CLEANUP_FILES[1]}') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) +@pytest.mark.parametrize("input_yaml, expected_files", [ + ("custom_reliability_diagram.yaml", ["custom_reliability_diagram.png"]), + ("custom_reliability_points1.yaml", ["reliability_points1.png", "intermed_files/reliability.points1"]), +]) +def test_files_exist(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot and data files are getting created""" -@pytest.mark.parametrize("test_input,expected", - (["intermed_files/reliability.png", True], ["intermed_files/reliability.points1", True])) -def test_files_exist(setup_env, test_input, expected, remove_files): - ''' - Checking that the plot and data files are getting created - ''' - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass + remove_files(os.environ['TEST_OUTPUT'], expected_files) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_reliability_points1.yaml" - r.main(custom_config_filename) + r.main(f"{cwd}/{input_yaml}") - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, ['intermed_files/reliability.png', 'intermed_files/reliability.points1']) - try: - os.rmdir(os.path.join(cwd, 'intermed_files')) - except OSError: - pass + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/revision_box/custom2_revision_box.yaml b/test/revision_box/custom2_revision_box.yaml deleted file mode 100644 index 2334f4f8..00000000 --- a/test/revision_box/custom2_revision_box.yaml +++ /dev/null @@ -1,136 +0,0 @@ -alpha: 0.05 -box_avg: 'False' -box_boxwex: 0.2 -box_notch: 'False' -box_outline: 'True' -box_pts: 'False' -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -circular_block_bootstrap: 'True' -cl_step: 0.05 -colors: -- '#ff0000' -- '#8000ff' -con_series: -- 1 -- 1 -create_html: 'False' -dump_points_1: 'True' -equalize_by_indep: 'True' -event_equal: 'True' -fcst_var_val_1: - TMP: - - ME -fixed_vars_vals_input: - interp_mthd: - interp_mthd_1: - - BILIN - fcst_lev: - fcst_lev_2: - - Z2 - vx_mask: - vx_mask_0: - - CONUS -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: -- '2011-07-02 03:00:00' -- '2011-07-02 06:00:00' -- '2011-07-02 09:00:00' -- '2011-07-02 12:00:00' -- '2011-07-02 15:00:00' -- '2011-07-02 18:00:00' -- '2011-07-02 21:00:00' -indy_plot_val: [] -indy_vals: -- '2011-07-02 03:00:00' -- '2011-07-02 06:00:00' -- '2011-07-02 09:00:00' -- '2011-07-02 12:00:00' -- '2011-07-02 15:00:00' -- '2011-07-02 18:00:00' -- '2011-07-02 21:00:00' -indy_var: fcst_valid_beg -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: None -list_stat_1: -- ME -mar: -- 8.0 -- 4.0 -- 5.0 -- 4.0 -method: bca -mgp: -- 1.0 -- 1.0 -- 0.0 - -plot_caption: '' -plot_disp: -- 'True' -- 'True' -plot_filename: !ENV '${TEST_DIR}/intermed_files/revision_box.png' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -random_seed: null -revision_ac: 'True' -revision_run: 'False' -series_order: -- 1 -- 2 -series_val_1: - model: - - AFWAOCv3.5.1_d01 - - NoahMPv3.5.1_d01 -stat_input: !ENV '${TEST_DIR}/revision_box.data' -title: test title -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] -variance_inflation_factor: 'True' -xaxis: test x_label -xaxis_reverse: 'False' -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -yaxis_1: test y_label -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 - -points_path: !ENV '${TEST_DIR}/intermed_files' -show_legend: -- 'True' -- 'True' \ No newline at end of file diff --git a/test/revision_box/custom_revision_box.yaml b/test/revision_box/custom_revision_box.yaml index fed2f274..2ae6a252 100644 --- a/test/revision_box/custom_revision_box.yaml +++ b/test/revision_box/custom_revision_box.yaml @@ -1,3 +1,8 @@ +plot_filename: !ENV '${TEST_OUTPUT}/revision_box.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + +stat_input: !ENV '${TEST_DIR}/revision_box.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -82,7 +87,6 @@ plot_caption: '' plot_disp: - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/revision_box.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -99,7 +103,6 @@ series_val_1: model: - AFWAOCv3.5.1_d01 - NoahMPv3.5.1_d01 -stat_input: !ENV '${TEST_DIR}/revision_box.data' title: test title title_align: 0.5 title_offset: -2 diff --git a/test/revision_box/test_revision_box.py b/test/revision_box/test_revision_box.py index 6932327f..95f42919 100644 --- a/test/revision_box/test_revision_box.py +++ b/test/revision_box/test_revision_box.py @@ -1,62 +1,19 @@ import pytest import os from metplotpy.plots.revision_box import revision_box -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['revision_box.png', 'revision_box.points1'] +def test_custom_revision_box(module_setup_env, remove_files): + """Checking that the plot and data files are getting created""" + expected_files = ( + 'revision_box.png', + 'intermed_files/revision_box.points1' + ) -@pytest.fixture -def setup(remove_files, setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_revision_box.yaml" + remove_files(os.environ['TEST_OUTPUT'], expected_files) - # Invoke the command to generate a Revision Box plot based on - # the custom_revision_box.yaml custom config file. - revision_box.main(custom_config_filename) + revision_box.main(f"{cwd}/custom_revision_box.yaml") - -@pytest.mark.parametrize("test_input, expected", - (["revision_box.png", True], ["revision_box.points1", True])) -def test_files_exist(setup, test_input, expected, remove_files): - """ - Checking that the plot and data files are getting created - """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup, remove_files): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages(f'{cwd}/revision_box_expected.png', f'{cwd}/revision_box.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input, expected", - (["intermed_files/revision_box.png", True], ["intermed_files/revision_box.points1", True])) -def test_files_exist(setup_env, test_input, expected, remove_files): - """ - Checking that the plot and data files are getting created - """ - setup_env(cwd) - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - custom_config_filename = f"{cwd}/custom2_revision_box.yaml" - - # Invoke the command to generate a Bar plot based on - # the custom_ens_ss.yaml custom config file. - revision_box.main(custom_config_filename) - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/revision_series/custom2_revision_series.yaml b/test/revision_series/custom2_revision_series.yaml deleted file mode 100644 index 9c05a30b..00000000 --- a/test/revision_series/custom2_revision_series.yaml +++ /dev/null @@ -1,131 +0,0 @@ -alpha: 0.05 -caption_align: 0.0 -caption_col: '#333333' -caption_offset: 3.0 -caption_size: 0.8 -caption_weight: 1 -cex: 1 -circular_block_bootstrap: 'True' -cl_step: 0.05 -colors: -- '#ff0000' -- '#8000ff' -con_series: -- 1 -- 1 -create_html: 'True' -dump_points_1: 'True' -equalize_by_indep: 'False' -event_equal: 'False' -fcst_var_val_1: - TMP: - - ME -fixed_vars_vals_input: {} -grid_col: '#cccccc' -grid_lty: 3 -grid_lwd: 1 -grid_on: 'True' -grid_x: listX -indy_label: -- '2011-07-02 03:00:00' -- '2011-07-02 06:00:00' -- '2011-07-02 09:00:00' -- '2011-07-02 12:00:00' -- '2011-07-02 15:00:00' -- '2011-07-02 18:00:00' -- '2011-07-02 21:00:00' -indy_plot_val: [] -indy_vals: -- '2011-07-02 03:00:00' -- '2011-07-02 06:00:00' -- '2011-07-02 09:00:00' -- '2011-07-02 12:00:00' -- '2011-07-02 15:00:00' -- '2011-07-02 18:00:00' -- '2011-07-02 21:00:00' -indy_var: fcst_valid_beg -legend_box: o -legend_inset: - x: 0.0 - y: -0.25 -legend_ncol: 3 -legend_size: 0.8 -line_type: None -list_stat_1: -- ME -mar: -- 8.0 -- 4.0 -- 5.0 -- 4.0 -method: bca -mgp: -- 1.0 -- 1.0 -- 0.0 -num_iterations: 1 -num_threads: -1 -plot_caption: '' -plot_disp: -- 'True' -- 'True' -plot_filename: !ENV '${TEST_DIR}/intermed_files/revision_series.png' -plot_height: 8.5 -plot_res: 72 -plot_stat: median -plot_type: png16m -plot_units: in -plot_width: 11.0 -random_seed: null -revision_ac: 'True' -revision_run: 'True' - -series_order: -- 1 -- 2 -series_symbols: -- . -- . -series_type: -- p -- b -series_val_1: - model: - - AFWAOCv3.5.1_d01 - - NoahMPv3.5.1_d01 - -start_from_zero: 'False' -stat_input: !ENV '${TEST_DIR}/revision_series.data' -title: Revision series -title_align: 0.5 -title_offset: -2 -title_size: 1.4 -title_weight: 2.0 -user_legend: [] -variance_inflation_factor: 'False' -vert_plot: 'False' -xaxis: fcst_valid_beg -xlab_align: 0.5 -xlab_offset: 2 -xlab_size: 1 -xlab_weight: 1 -xlim: [] -xtlab_decim: 0 -xtlab_horiz: 0.5 -xtlab_orient: 1 -xtlab_perp: -0.75 -xtlab_size: 1 -yaxis_1: ME -ylab_align: 0.5 -ylab_offset: -2 -ylab_size: 1 -ylab_weight: 1 -ylim: [] -ytlab_horiz: 0.5 -ytlab_orient: 1 -ytlab_perp: 0.5 -ytlab_size: 1 -points_path: !ENV '${TEST_DIR}/intermed_files' -show_legend: -- 'True' -- 'True' \ No newline at end of file diff --git a/test/revision_series/custom_revision_series.yaml b/test/revision_series/custom_revision_series.yaml index 30ddf814..66b38deb 100644 --- a/test/revision_series/custom_revision_series.yaml +++ b/test/revision_series/custom_revision_series.yaml @@ -1,3 +1,8 @@ +plot_filename: !ENV '${TEST_OUTPUT}/revision_series.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + +stat_input: !ENV '${TEST_DIR}/revision_series.data' + alpha: 0.05 caption_align: 0.0 caption_col: '#333333' @@ -69,7 +74,6 @@ plot_caption: '' plot_disp: - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/revision_series.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -95,7 +99,6 @@ series_val_1: - NoahMPv3.5.1_d01 start_from_zero: 'False' -stat_input: !ENV '${TEST_DIR}/revision_series.data' title: Revision series title_align: 0.5 title_offset: -2 diff --git a/test/revision_series/test_revision_series.py b/test/revision_series/test_revision_series.py index c381900a..94cf0865 100644 --- a/test/revision_series/test_revision_series.py +++ b/test/revision_series/test_revision_series.py @@ -1,64 +1,18 @@ import pytest import os from metplotpy.plots.revision_series import revision_series -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ('revision_series.png', 'revision_series.points1') +@pytest.mark.parametrize("input_yaml, expected_files", [ + ("custom_revision_series.yaml", ["revision_series.png", "intermed_files/revision_series.points1"]), +]) +def test_files_exist(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot and data files are getting created""" -@pytest.fixture -def setup(remove_files, setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_revision_series.yaml" + remove_files(os.environ['TEST_OUTPUT'], expected_files) - # Invoke the command to generate a Revision Series plot based on - # the custom_revision_series.yaml custom config file. - revision_series.main(custom_config_filename) - - -@pytest.mark.parametrize("test_input, expected", - (["revision_series.png", True], ["revision_series.points1", True])) -def test_files_exist(setup, test_input, expected, remove_files): - """ - Checking that the plot and data files are getting created - """ - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("fails on linux hosts") -def test_images_match(setup, remove_files): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - comparison = CompareImages('./revision_series_expected.png', './revision_series.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input, expected", - (["intermed_files/revision_series.png", True], - ["intermed_files/revision_series.points1", True])) -def test_files_exist(setup_env, test_input, expected, remove_files): - """ - Checking that the plot and data files are getting created - """ - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - setup_env(cwd) - custom_config_filename = f"{cwd}/custom2_revision_series.yaml" - - # Invoke the command to generate a Bar plot based on - # the custom_ens_ss.yaml custom config file. - revision_series.main(custom_config_filename) - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) + revision_series.main(f"{cwd}/{input_yaml}") + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/roc_diagram/CTC_ROC.yaml b/test/roc_diagram/CTC_ROC.yaml index f4a72858..15cc2797 100644 --- a/test/roc_diagram/CTC_ROC.yaml +++ b/test/roc_diagram/CTC_ROC.yaml @@ -1,4 +1,6 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC.png' +stat_input: !ENV '${TEST_DIR}/CTC_ROC.data' add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -62,7 +64,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -88,7 +89,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/CTC_ROC_ee.yaml b/test/roc_diagram/CTC_ROC_ee.yaml index 4391d2fd..face525e 100644 --- a/test/roc_diagram/CTC_ROC_ee.yaml +++ b/test/roc_diagram/CTC_ROC_ee.yaml @@ -1,4 +1,7 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC_ee.png' +stat_input: !ENV '${TEST_DIR}/CTC_ROC.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -62,7 +65,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC_ee.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -88,7 +90,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/CTC_ROC_summary.yaml b/test/roc_diagram/CTC_ROC_summary.yaml index e24ca49a..f8c7d36f 100644 --- a/test/roc_diagram/CTC_ROC_summary.yaml +++ b/test/roc_diagram/CTC_ROC_summary.yaml @@ -1,3 +1,8 @@ +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC_summary.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + +stat_input: !ENV '${TEST_DIR}/CTC_ROC_summary.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -77,7 +82,6 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC_summary.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -123,7 +127,6 @@ show_signif: - 'False' - 'False' start_from_zero: 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC_summary.data' summary_curve: median sync_yaxes: 'False' title: test title @@ -173,6 +176,4 @@ ylim: [] ytlab_horiz: 0.5 ytlab_orient: 1 ytlab_perp: 0.5 -ytlab_size: 1 -points_path: !ENV '${TEST_DIR}/intermed_files' - +ytlab_size: 1 \ No newline at end of file diff --git a/test/roc_diagram/CTC_ROC_thresh.yaml b/test/roc_diagram/CTC_ROC_thresh.yaml index 7bbc26ca..9a1c5bb6 100644 --- a/test/roc_diagram/CTC_ROC_thresh.yaml +++ b/test/roc_diagram/CTC_ROC_thresh.yaml @@ -1,4 +1,7 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC_thresh.png' +stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -64,7 +67,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC_thresh.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -90,7 +92,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/CTC_ROC_thresh_dump_pts.yaml b/test/roc_diagram/CTC_ROC_thresh_dump_pts.yaml index 3432b2ae..bf8be3f9 100644 --- a/test/roc_diagram/CTC_ROC_thresh_dump_pts.yaml +++ b/test/roc_diagram/CTC_ROC_thresh_dump_pts.yaml @@ -1,4 +1,9 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC_thresh_dump_pts.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files' + +stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -64,14 +69,12 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC_thresh_dump_pts.png' plot_height: 8.5 plot_res: 72 plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_DIR}/intermed_files' random_seed: null reverse_connection_order: 'True' roc_ctc: true @@ -91,7 +94,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/CTC_ROC_thresh_reverse_pts.yaml b/test/roc_diagram/CTC_ROC_thresh_reverse_pts.yaml index d6d0e4a7..a73d8ae1 100644 --- a/test/roc_diagram/CTC_ROC_thresh_reverse_pts.yaml +++ b/test/roc_diagram/CTC_ROC_thresh_reverse_pts.yaml @@ -1,4 +1,9 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_ROC_thresh_reverse_pts.png' +points_path: !ENV '${TEST_OUTPUT}/intermed_files_reverse_pts' +stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' + + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -64,7 +69,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_ROC_thresh.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -90,7 +94,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/CTC_wind_reformatted.yaml b/test/roc_diagram/CTC_wind_reformatted.yaml index 3874c2d2..f0414ead 100644 --- a/test/roc_diagram/CTC_wind_reformatted.yaml +++ b/test/roc_diagram/CTC_wind_reformatted.yaml @@ -1,4 +1,7 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/CTC_wind_reformatted.png' +stat_input: !ENV '${TEST_DIR}/ctc_reformatted_wind_P500.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -64,7 +67,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/CTC_wind_reformatted.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -90,7 +92,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/ctc_reformatted_wind_P500.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/roc_diagram/PCT_ROC.yaml b/test/roc_diagram/PCT_ROC.yaml index 8d0b8b64..ab78006e 100644 --- a/test/roc_diagram/PCT_ROC.yaml +++ b/test/roc_diagram/PCT_ROC.yaml @@ -1,4 +1,7 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/PCT_ROC.png' +stat_input: !ENV '${TEST_DIR}/PCT_ROC.data' + add_point_thresholds: 'True' alpha: 0.05 box_avg: 'False' @@ -62,7 +65,6 @@ plot_ci: - none plot_disp: - 'True' -plot_filename: !ENV '${TEST_DIR}/PCT_ROC.png' plot_height: 8.5 plot_res: 72 plot_stat: median @@ -88,7 +90,6 @@ series_val_2: {} show_nstats: 'False' show_signif: - 'False' -stat_input: !ENV '${TEST_DIR}/PCT_ROC.data' sync_yaxes: 'False' title: test title PCT ROC data diff --git a/test/roc_diagram/test_roc_diagram.py b/test/roc_diagram/test_roc_diagram.py index a5c22d37..b44ea918 100644 --- a/test/roc_diagram/test_roc_diagram.py +++ b/test/roc_diagram/test_roc_diagram.py @@ -2,115 +2,50 @@ import pytest import os + import pandas as pd + from metplotpy.plots.roc_diagram import roc_diagram as roc -# from metcalcpy.compare_images import CompareImages import metcalcpy.util.ctc_statistics as ctc cwd = os.path.dirname(__file__) -# Fixture used for the image comparison -# test. -@pytest.fixture -def setup(setup_env): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - setup_env(cwd) - custom_config_filename = f"{cwd}/CTC_ROC_thresh.yaml" - # print("\n current directory: ", os.getcwd()) - # print("\ncustom config file: ", custom_config_filename, '\n') - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - roc.main(custom_config_filename) - - -@pytest.fixture -def setup_rev_points(setup_env): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - setup_env(cwd) - custom_config_filename = f"{cwd}/CTC_ROC_thresh_reverse_pts.yaml" - print("\n current directory: ", cwd) - print("\ncustom config file: ", custom_config_filename, '\n') - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - roc.main(custom_config_filename) - - -@pytest.fixture -def setup_summary(setup_env): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - setup_env(cwd) - custom_config_filename = f"{cwd}/CTC_ROC_summary.yaml" - - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - roc.main(custom_config_filename) - - -@pytest.fixture -def setup_dump_points(setup_env): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() - setup_env(cwd) - # put any intermediate files in the intermed_files subdirectory of this current - # working directory. *NOTE* This MUST match with what you set up in CTC_ROC_thresh.yaml for the - # points_path configuration setting. - custom_config_filename = f"{cwd}/CTC_ROC_thresh_dump_pts.yaml" - # print("\n current directory: ", os.getcwd()) - # print("\ncustom config file: ", custom_config_filename, '\n') - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - roc.main(custom_config_filename) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("CTC_ROC_thresh.yaml", ["CTC_ROC_thresh.png"]), + ("CTC_ROC_thresh_dump_pts.yaml", ["CTC_ROC_thresh_dump_pts.png", "intermed_files/CTC_ROC_thresh.points1"]), + ("CTC_ROC_summary.yaml", ["CTC_ROC_summary.png", "intermed_files/CTC_ROC_summary.points1"]), + ("CTC_ROC_thresh_reverse_pts.yaml", ["CTC_ROC_thresh_reverse_pts.png", "intermed_files_reverse_pts/CTC_ROC_thresh.points1"]), + ("PCT_ROC.yaml", ["PCT_ROC.png", "PCT_ROC.html"]), + ("CTC_wind_reformatted.yaml", ["CTC_wind_reformatted.png", "CTC_wind_reformatted.html"]), +]) +def test_roc_diagram(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created but the points1 file is NOT""" + remove_files(os.environ['TEST_OUTPUT'], expected_files) -def cleanup(): - # remove the performance_diagram_expected.png and plot_20200317_151252.points1 files - # from any previous runs - # The subdir_path is where the .points1 file will be stored - try: - plot_file = 'CTC_ROC_thresh.png' - html_file = 'CTC_ROC_thresh.html' - os.remove(os.path.join(cwd, html_file)) - os.remove(os.path.join(cwd, plot_file)) - except OSError: - pass + roc.main(f"{cwd}/{input_yaml}") + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") -@pytest.mark.parametrize("test_input,expected_boolean", - (["CTC_ROC_thresh_expected.png", True], ["CTC_ROC_thresh.points1", False])) -def test_files_exist(setup, test_input, expected_boolean): - ''' - Checking that the plot file is getting created but the points1 file is NOT - ''' - assert os.path.isfile(f"{cwd}/{test_input}") == expected_boolean - cleanup() + if input_yaml == "CTC_ROC_thresh_dump_pts.yaml": + check_ctc_thresh_dump_points() + if input_yaml == "CTC_ROC_summary.yaml": + check_expected_ctc_summary() + if input_yaml == "CTC_ROC_thresh_reverse_pts.yaml": + check_expected_ctc_thresh_points_reversed() -def test_expected_ctc_thresh_dump_points(setup_dump_points, remove_files): - ''' +def check_ctc_thresh_dump_points(): + """ For test data, verify that the points in the .points1 file are in the - directory we specified and the values - match what is expected (within round-off tolerance/acceptable precision). - :return: - ''' + directory we specified and the values match what is expected + (within round-off tolerance/acceptable precision). + """ expected_pody = pd.Series([1, 0.8457663, 0.7634846, 0.5093934, 0.1228585, 0]) expected_pofd = pd.Series([1, 0.0688293, 0.049127, 0.0247044, 0.0048342, 0]) - df = pd.read_csv(f"{cwd}/intermed_files/CTC_ROC_thresh.points1", sep='\t', header='infer') + df = pd.read_csv(f"{os.environ['TEST_OUTPUT']}/intermed_files/CTC_ROC_thresh.points1", sep='\t', header='infer') pofd = df.iloc[:, 0] pody = df.iloc[:, 1] @@ -123,22 +58,17 @@ def test_expected_ctc_thresh_dump_points(setup_dump_points, remove_files): for index, expected in enumerate(expected_pofd): assert ctc.round_half_up(expected) - ctc.round_half_up(pofd[index]) == 0.0 - # different cleanup than what is provided by cleanup() - # clean up the intermediate subdirectory and other files - remove_files(cwd, ['CTC_ROC_thresh_dump_pts.png', 'CTC_ROC_thresh_dump_pts.html']) - -def test_expected_ctc_summary(setup_summary, remove_files): - ''' +def check_expected_ctc_summary(): + """ For test data, verify that the points in the .points1 file are in the directory we specified and the values match what is expected (within round-off tolerance/acceptable precision). - :return: - ''' + """ expected_pofd = pd.Series([1, 0.0052708, 0, 1, 0.0084788, 0, 1, 0.0068247, 0]) expected_pody = pd.Series([1, 0.0878715, 0, 1, 0.1166785, 0, 1, 0.1018776, 0]) - df = pd.read_csv(f"{cwd}/intermed_files/CTC_ROC_summary.points1", sep='\t', header='infer') + df = pd.read_csv(f"{os.environ['TEST_OUTPUT']}/intermed_files/CTC_ROC_summary.points1", sep='\t', header='infer') pofd = df.iloc[:, 0] pody = df.iloc[:, 1] @@ -151,12 +81,8 @@ def test_expected_ctc_summary(setup_summary, remove_files): for index, expected in enumerate(expected_pofd): assert ctc.round_half_up(expected) - ctc.round_half_up(pofd[index]) == 0.0 - # different cleanup than what is provided by cleanup() - # clean up the intermediate subdirectory and other files - remove_files(cwd, ['CTC_ROC_summary.png', 'CTC_ROC_summary.html']) - -def test_expected_ctc_thresh_points_reversed(setup_rev_points, remove_files): +def check_expected_ctc_thresh_points_reversed(): ''' For test data, verify that the points in the .points1 file match what is expected (within round-off tolerance/acceptable precision) when @@ -166,7 +92,7 @@ def test_expected_ctc_thresh_points_reversed(setup_rev_points, remove_files): expected_pody = pd.Series([1, 0.8457663, 0.7634846, 0.5093934, 0.1228585, 0]) expected_pofd = pd.Series([1, 0.0688293, 0.0491275, 0.0247044, 0.0048342, 0]) - df = pd.read_csv(f"{cwd}/CTC_ROC_thresh.points1", sep='\t', header='infer') + df = pd.read_csv(f"{os.environ['TEST_OUTPUT']}/intermed_files_reverse_pts/CTC_ROC_thresh.points1", sep='\t', header='infer') pofd = df.iloc[:, 0] pody = df.iloc[:, 1] @@ -179,104 +105,44 @@ def test_expected_ctc_thresh_points_reversed(setup_rev_points, remove_files): for index, expected in enumerate(expected_pofd): assert ctc.round_half_up(expected) - ctc.round_half_up(pofd[index]) == 0.0 - # if we get here, then all elements matched in value and position - - # different cleanup than what is provided by cleanup() - remove_files(cwd, ['CTC_ROC_thresh.png', 'CTC_ROC_thresh.points1', 'CTC_ROC_thresh.html']) - -def test_ee_returns_empty_df(capsys, remove_files): - ''' +def test_ee_returns_empty_df(module_setup_env, capsys, remove_files): + """ use CTC_ROC.data with event equalization set to True. This will result in an empty data frame returned from event equalization. Check for expected output message: "INFO: No resulting data after performing event equalization of axis 1 INFO: No points to plot (most likely as a result of event equalization). " + """ + expected_files = ['CTC_ROC_ee.png', 'CTC_ROC_ee.html'] + remove_files(os.environ['TEST_OUTPUT'], expected_files) - - ''' custom_config_filename = f"{cwd}/CTC_ROC_ee.yaml" + roc.main(custom_config_filename) + + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") + captured = capsys.readouterr() expected = '\nINFO: No resulting data after performing event equalization of axis 1\n' \ 'INFO: No points to plot (most likely as a result of event equalization). \n' - # print('\n\noutput from capsys: ', captured.out) - # print('\nexpected:', expected) - assert captured.out == expected + assert expected in captured.out - # Clean up - remove_files(cwd, ['CTC_ROC_ee.png', 'CTC_ROC_ee.html']) - -@pytest.mark.skip("skip image comparison") -def test_images_match(setup, remove_files): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - - !!!!!WARNING!!!!!: When run within PyCharm IDE, the CTC_ROC_thresh.png plot - can sometimes be a different size than the expected (which was generated - using the same configuration file and data!) - ''' - plot_file = 'CTC_ROC_thresh.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/CTC_ROC_thresh.png', actual_file) - assert comparison.mssim == 1 - - # cleanup - # different cleanup than what is provided by cleanup() - remove_files(cwd, ['CTC_ROC_thresh.png', 'CTC_ROC_thresh.html']) - - -def test_pct_plot_exists(remove_files): - ''' - Verify that the ROC diagram is generated - ''' - - custom_config_filename = f"{cwd}/PCT_ROC.yaml" - output_plot = f"{cwd}/PCT_ROC.png" - - print("\n Testing for existence of PCT ROC plot...") - roc.main(custom_config_filename) - assert os.path.isfile(output_plot) - remove_files(cwd, ['PCT_ROC.png', 'PCT_ROC.html']) - - -def test_pct_no_warnings(remove_files): +def test_pct_no_warnings(module_setup_env, remove_files): ''' Verify that the ROC diagram is generated without FutureWarnings ''' + remove_files(os.environ['TEST_OUTPUT'], ['PCT_ROC.png', 'PCT_ROC.html']) + custom_config_filename = f"{cwd}/PCT_ROC.yaml" print("\n Testing for FutureWarning..") - try: roc.main(custom_config_filename) except FutureWarning: print("FutureWarning generated") # FutureWarning generated, test fails assert False - - remove_files(cwd, ['PCT_ROC.png', 'PCT_ROC.html']) - - -def test_ctc_reformatted(remove_files): - ''' - Verify that the ROC diagram is generated successfully - from reformatted CTC data. - ''' - - custom_config_filename = f"{cwd}/CTC_wind_reformatted.yaml" - output_plot = f"{cwd}/CTC_wind_reformatted.png" - - print("\n Testing for presence of the CTC_wind_reformatted.png plot...") - - roc.main(custom_config_filename) - assert os.path.isfile(output_plot) - # Checking for plot size isn't reliable - #expected_filesize = int(43239) - #plot_filesize = int(os.path.getsize(output_plot)) - #assert plot_filesize >= expected_filesize - remove_files(cwd, ['CTC_wind_reformatted.png', 'CTC_wind_reformatted.html']) diff --git a/test/roc_diagram/test_roc_diagram.yaml b/test/roc_diagram/test_roc_diagram.yaml index 4ccee027..24339347 100644 --- a/test/roc_diagram/test_roc_diagram.yaml +++ b/test/roc_diagram/test_roc_diagram.yaml @@ -1,4 +1,8 @@ --- +plot_filename: !ENV '${TEST_OUTPUT}/roc_diagram_actual.png' + +stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' + roc_pct: False roc_ctc: True @@ -91,7 +95,5 @@ caption_align: .1 #0 # axis parallel location adjustment # Make the plot generated in METviewer interactive create_html: 'True' -# input file is relative to the roc_diagram.py module -stat_input: !ENV '${TEST_DIR}/CTC_ROC_thresh.data' -plot_filename: !ENV '${TEST_DIR}/roc_diagram_actual.png' + From 96d57ff3142d718e4dfadc60c649076fd3fbb5be Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:04:13 -0700 Subject: [PATCH 16/31] create directories before writing files to prevent failures when directory does not already exist --- metplotpy/plots/mpr_plot/mpr_plot.py | 5 +- .../performance_diagram.py | 48 ++++++++----------- .../plots/reliability_diagram/reliability.py | 1 + metplotpy/plots/revision_box/revision_box.py | 1 + .../plots/revision_series/revision_series.py | 2 +- metplotpy/plots/roc_diagram/roc_diagram.py | 47 ++++++++---------- 6 files changed, 44 insertions(+), 60 deletions(-) diff --git a/metplotpy/plots/mpr_plot/mpr_plot.py b/metplotpy/plots/mpr_plot/mpr_plot.py index 329d5b27..90a77fcc 100644 --- a/metplotpy/plots/mpr_plot/mpr_plot.py +++ b/metplotpy/plots/mpr_plot/mpr_plot.py @@ -13,7 +13,7 @@ """ __author__ = 'Tatiana Burek' - +import os from datetime import datetime import pandas as pd import numpy as np @@ -562,13 +562,14 @@ def save_to_file(self) -> None: Returns: """ - self.logger.info(f"Saving to file") + self.logger.info("Saving to file") image_name = self.get_config_value('plot_filename') pio.kaleido.scope.default_format = "png" pio.kaleido.scope.default_height = self.config_obj.height pio.kaleido.scope.default_width = self.config_obj.width if self.figure: try: + os.makedirs(os.path.dirname(image_name), exist_ok=True) self.figure.write_image(image_name) except FileNotFoundError: self.logger.error(f"FileNotFoundError: {image_name}") diff --git a/metplotpy/plots/performance_diagram/performance_diagram.py b/metplotpy/plots/performance_diagram/performance_diagram.py index e5f92ed2..a9d96d30 100644 --- a/metplotpy/plots/performance_diagram/performance_diagram.py +++ b/metplotpy/plots/performance_diagram/performance_diagram.py @@ -425,36 +425,26 @@ def write_output_file(self): filename = self.config_obj.points_path + os.path.sep + filename output_file = filename + '.points1' + os.makedirs(os.path.dirname(output_file), exist_ok=True) + if os.path.exists(output_file): + os.remove(output_file) + + with open(output_file, 'a') as fileobj: + header_str = "1-far\t pody\n" + fileobj.write(header_str) + all_pody = [] + all_sr = [] + for series in self.series_list: + pody_points = series.series_points[1] + sr_points = series.series_points[0] + all_pody.extend(pody_points) + all_sr.extend(sr_points) + + all_points = zip(all_sr, all_pody) + for idx, pts in enumerate(all_points): + data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" + fileobj.write(data_str) - # make sure this file doesn't already - # exist, delete it if it does - try: - if os.stat(output_file).st_size == 0: - fileobj = open(output_file, 'a') - else: - os.remove(output_file) - except FileNotFoundError: - # OK if no file was found - self.logger.error("FileNotFoundError: OK to ignore, deleting files " - "and the file to delete is not found") - - fileobj = open(output_file, 'a') - header_str = "1-far\t pody\n" - fileobj.write(header_str) - all_pody = [] - all_sr = [] - for series in self.series_list: - pody_points = series.series_points[1] - sr_points = series.series_points[0] - all_pody.extend(pody_points) - all_sr.extend(sr_points) - - all_points = zip(all_sr, all_pody) - for idx, pts in enumerate(all_points): - data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" - fileobj.write(data_str) - - fileobj.close() self.logger.info(f"Finished writing output file: {datetime.now()}") diff --git a/metplotpy/plots/reliability_diagram/reliability.py b/metplotpy/plots/reliability_diagram/reliability.py index aebd0fa9..fc928af7 100644 --- a/metplotpy/plots/reliability_diagram/reliability.py +++ b/metplotpy/plots/reliability_diagram/reliability.py @@ -713,6 +713,7 @@ def _save_points(points: list, output_file: str) -> None: else: formatted_row.append("%.6f" % val) all_points_formatted.append(formatted_row) + os.makedirs(os.path.dirname(output_file), exist_ok=True) with open(output_file, "w+") as my_csv: csv_writer = csv.writer(my_csv, delimiter=' ') csv_writer.writerows(all_points_formatted) diff --git a/metplotpy/plots/revision_box/revision_box.py b/metplotpy/plots/revision_box/revision_box.py index d9336712..bfca2af4 100644 --- a/metplotpy/plots/revision_box/revision_box.py +++ b/metplotpy/plots/revision_box/revision_box.py @@ -268,6 +268,7 @@ def write_output_file(self) -> None: filename = self.config_obj.points_path + os.path.sep + filename filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) if os.path.exists(filename): os.remove(filename) for series in self.series_list: diff --git a/metplotpy/plots/revision_series/revision_series.py b/metplotpy/plots/revision_series/revision_series.py index acf7621e..03e6cafd 100644 --- a/metplotpy/plots/revision_series/revision_series.py +++ b/metplotpy/plots/revision_series/revision_series.py @@ -17,7 +17,6 @@ from typing import Union -import yaml import numpy as np import plotly.graph_objects as go @@ -280,6 +279,7 @@ def write_output_file(self) -> None: filename = 'points' filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as file: for series in self.series_list: file.writelines( diff --git a/metplotpy/plots/roc_diagram/roc_diagram.py b/metplotpy/plots/roc_diagram/roc_diagram.py index 8dea0e69..01bcdd81 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram.py +++ b/metplotpy/plots/roc_diagram/roc_diagram.py @@ -507,35 +507,26 @@ def write_output_file(self): filename = self.config_obj.points_path + os.path.sep + filename output_file = filename + '.points1' + os.makedirs(os.path.dirname(output_file), exist_ok=True) + if os.path.exists(output_file): + os.remove(output_file) + + with open(output_file, 'a') as fileobj: + header_str = "pofd\t pody\n" + fileobj.write(header_str) + all_pody = [] + all_pofd = [] + for series in self.series_list: + pody_points = series.series_points[1] + pofd_points = series.series_points[0] + all_pody.extend(pody_points) + all_pofd.extend(pofd_points) + + all_points = zip(all_pofd, all_pody) + for idx, pts in enumerate(all_points): + data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" + fileobj.write(data_str) - # make sure this file doesn't already - # exit, delete it if it does - try: - if os.stat(output_file).st_size == 0: - fileobj = open(output_file, 'a') - else: - os.remove(output_file) - except FileNotFoundError as fnfe: - # OK if no file was found - pass - - fileobj = open(output_file, 'a') - header_str = "pofd\t pody\n" - fileobj.write(header_str) - all_pody = [] - all_pofd = [] - for series in self.series_list: - pody_points = series.series_points[1] - pofd_points = series.series_points[0] - all_pody.extend(pody_points) - all_pofd.extend(pofd_points) - - all_points = zip(all_pofd, all_pody) - for idx, pts in enumerate(all_points): - data_str = str(pts[0]) + "\t" + str(pts[1]) + "\n" - fileobj.write(data_str) - - fileobj.close() def write_html(self) -> None: """ From 7d3c36450004b53e3bda60a0d0ba49e5dacbbd3d Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:08:49 -0700 Subject: [PATCH 17/31] update wind_rose and tcmpr_plots tests to write to output directory --- ...cmpr_multi_plots.yaml => tcmpr_multi.yaml} | 15 +- test/tcmpr_plots/tcmpr_multi_plots.yaml | 148 ------------------ test/tcmpr_plots/tcmpr_point_tcdiag.yaml | 15 +- test/tcmpr_plots/test_tcmpr_plots.py | 111 ++++--------- test/wind_rose/minimal_wind_rose.yaml | 8 +- test/wind_rose/test_wind_rose.py | 119 +++----------- test/wind_rose/wind_rose_custom.yaml | 20 +-- test/wind_rose/wind_rose_custom2_points.yaml | 36 ----- test/wind_rose/wind_rose_custom_points.yaml | 14 +- 9 files changed, 90 insertions(+), 396 deletions(-) rename test/tcmpr_plots/{test_tcmpr_multi_plots.yaml => tcmpr_multi.yaml} (82%) delete mode 100755 test/tcmpr_plots/tcmpr_multi_plots.yaml delete mode 100644 test/wind_rose/wind_rose_custom2_points.yaml diff --git a/test/tcmpr_plots/test_tcmpr_multi_plots.yaml b/test/tcmpr_plots/tcmpr_multi.yaml similarity index 82% rename from test/tcmpr_plots/test_tcmpr_multi_plots.yaml rename to test/tcmpr_plots/tcmpr_multi.yaml index f94694ec..0fb6fa0f 100755 --- a/test/tcmpr_plots/test_tcmpr_multi_plots.yaml +++ b/test/tcmpr_plots/tcmpr_multi.yaml @@ -1,3 +1,11 @@ +plot_dir: !ENV '${TEST_OUTPUT}/multi' +log_filename: !ENV '${TEST_OUTPUT}/multi/tcmpr_log.out' + +tcst_dir: !ENV '${TEST_DIR}/Data/' + +baseline_file: !ENV '${METPLOTPY_BASE}/metplotpy/plots/tcmpr_plots/hfip_baseline.dat' +column_info_file: !ENV '${METPLOTPY_BASE}/metplotpy/plots/tcmpr_plots/plot_tcmpr_hdr.dat' + colors: - 'blue' - 'green' @@ -102,8 +110,7 @@ series_symbols_size: - 7 show_nstats: 'True' -tcst_dir: !ENV '${TEST_DIR}/Data/' -#tcst_dir: '/path/to/tcmpr_sample_data' + tcst_files: [ ] title: '' @@ -133,13 +140,9 @@ event_equal: 'True' skill_ref: - HFSA -plot_dir: !ENV '${TEST_DIR}/output' prefix: '' subtitle: log_level: INFO -log_filename: !ENV '${TEST_DIR}/output/tcmpr_log.out' -baseline_file: './metplotpy/plots/tcmpr_plots/hfip_baseline.dat' -column_info_file: './metplotpy/plots/tcmpr_plots/plot_tcmpr_hdr.dat' hfip_bsln: 'no' diff --git a/test/tcmpr_plots/tcmpr_multi_plots.yaml b/test/tcmpr_plots/tcmpr_multi_plots.yaml deleted file mode 100755 index f444bcc1..00000000 --- a/test/tcmpr_plots/tcmpr_multi_plots.yaml +++ /dev/null @@ -1,148 +0,0 @@ -colors: - - 'blue' - - 'green' - -fixed_vars_vals_input: - BASIN: - - AL - LEVEL: - - SS - - SD - - TS - - TD - - HU - - -indy_vals: - - 0 - - 6 - - 12 - - 18 - - 24 - - 30 - - 36 - - 42 - - 48 - - 54 - - 60 - - 66 - - 72 - - 78 - - 84 - - 90 - - 96 - - 102 - - 108 - - 114 - - 120 - - 126 -indy_label: - - '00' - - '06' - - '12' - - '18' - - '24' - - '30' - - '36' - - '42' - - '48' - - '54' - - '60' - - '66' - - '72' - - '78' - - '84' - - '90' - - '96' - - '102' - - '108' - - '114' - - '120' - - '126' - - -list_stat_1: - - "ABS(AMAX_WIND-BMAX_WIND)" - - "TK_ERR" - - -plot_disp: - - 'True' - - 'True' - - -series_order: - - 1 - - 2 - - -series_val_1: - AMODEL: - - H221 - - M221 - -series_ci: - - 'True' - - 'True' - -series_line_width: - - 1 - - 1 - -series_line_style: - - '-' - - '-' - -series_symbols: - - 'circle-open' - - 'circle-open' - -series_symbols_size: - - 7 - - 7 - -show_nstats: 'True' -tcst_dir: '/path/to/tcmpr_sample_data' -tcst_files: [ ] -title: '' - -mar: - l: 0 #left margin - r: 0 #right margin - b: 90 #bottom margin - t: 100 #top margin - -title_offset: -2.03 -yaxis_1: '' - -plot_type_list: - - 'boxplot' - - 'skill_mn' - - 'skill_md' - - 'relperf' - - 'mean' - - 'median' - - 'rank' - - -rp_diff: -# - '>=1' - - '>=100' -event_equal: 'True' -skill_ref: - - HFSA - -plot_dir: '/path/to/output' -prefix: '' -subtitle: -log_level: INFO -log_filename: '/path/to/log/output/tcmpr_log.out' -baseline_file: 'path/to/metplotpy/plots/tcmpr_plots/hfip_baseline.dat' -column_info_file: 'path/to/metplotpy/plots/tcmpr_plots/plot_tcmpr_hdr.dat' -hfip_bsln: 'no' - - - - - - diff --git a/test/tcmpr_plots/tcmpr_point_tcdiag.yaml b/test/tcmpr_plots/tcmpr_point_tcdiag.yaml index 636e8d63..686e9de3 100755 --- a/test/tcmpr_plots/tcmpr_point_tcdiag.yaml +++ b/test/tcmpr_plots/tcmpr_point_tcdiag.yaml @@ -1,3 +1,11 @@ +plot_dir: !ENV '${TEST_OUTPUT}/point_tcdiag' +log_filename: !ENV '${TEST_OUTPUT}/point_tcdiag/tcmpr.log' + +baseline_file: !ENV '${METPLOTPY_BASE}/metplotpy/plots/tcmpr_plots/hfip_baseline.dat' +column_info_file: !ENV '${METPLOTPY_BASE}/metplotpy/plots/tcmpr_plots/plot_tcmpr_hdr.dat' + +tcst_dir: !ENV '${TEST_DIR}/Data/TCDiag/' + colors: # - 'green' - 'blue' @@ -72,9 +80,6 @@ series_symbols_size: - 7 - 7 - - -tcst_dir: './Data/TCDiag/' tcst_files: [ ] title: '' title_size: 1. @@ -88,11 +93,8 @@ plot_type_list: event_equal: 'False' - -plot_dir: './output/' prefix: '' - indy_vals: - 0 - 6 @@ -159,4 +161,3 @@ line_type: '-' connect_points: True log_level: INFO -log_filename: './output/tcmpr.log' \ No newline at end of file diff --git a/test/tcmpr_plots/test_tcmpr_plots.py b/test/tcmpr_plots/test_tcmpr_plots.py index 56f7764a..0165b3d2 100644 --- a/test/tcmpr_plots/test_tcmpr_plots.py +++ b/test/tcmpr_plots/test_tcmpr_plots.py @@ -1,85 +1,38 @@ import pytest import os -import metplotpy.plots.tcmpr_plots.tcmpr as t +import metplotpy.plots.tcmpr_plots.tcmpr as tcmpr cwd = os.path.dirname(__file__) - -@pytest.fixture -def setup(setup_env): - setup_env(cwd) - custom_config_filename = f"{cwd}/test_tcmpr_multi_plots.yaml" - - # Invoke the command to generate a line plot based on - # the custom config file - t.main(custom_config_filename) - - -def test_plots_created(setup): - # Check for presence of fourteen plots (seven plot types, one each for the - # ABS(X-Y) and TK_ERR columns. - expected_num_plots = 14 - output_dir = os.path.join(cwd, 'output') - only_files = os.listdir(output_dir) - assert len(only_files) == expected_num_plots - - size_abs_boxplot = 221448 - size_abs_mean = 173448 - size_abs_median = 184775 - size_abs_rank = 255637 - size_abs_relperf = 117889 - size_abs_skill_md = 198979 - size_abs_skill_mn = 185594 - size_tk_boxplot = 192259 - size_tk_mean = 146980 - size_tk_median = 152591 - size_tk_rank = 189702 - size_tk_relperf = 157972 - size_tk_skill_md = 171571 - size_tk_skill_mn = 159396 - expected_sizes = {'ABS(AMAX_WIND-BMAX_WIND)_boxplot.png':size_abs_boxplot, - 'ABS(AMAX_WIND-BMAX_WIND)_mean.png':size_abs_mean, - 'ABS(AMAX_WIND-BMAX_WIND)_median.png':size_abs_median, - 'ABS(AMAX_WIND-BMAX_WIND)_relperf.png':size_abs_relperf, - 'ABS(AMAX_WIND-BMAX_WIND)_skill_md.png':size_abs_skill_md, - 'ABS(AMAX_WIND-BMAX_WIND)_skill_mn.png':size_abs_skill_mn, - 'TK_ERR_boxplot.png':size_tk_boxplot, 'TK_ERR_mean.png':size_tk_mean, - 'TK_ERR_median.png':size_tk_median, - 'TK_ERR_relperf.png':size_tk_relperf, 'TK_ERR_skill_md.png':size_tk_skill_md, - 'TK_ERR_skill_mn.png':size_tk_skill_mn - } - - # Check that filenames are what we expect - expected_names_list = ['ABS(AMAX_WIND-BMAX_WIND)_boxplot.png', 'ABS(AMAX_WIND-BMAX_WIND)_mean.png', - 'ABS(AMAX_WIND-BMAX_WIND)_median.png', - 'ABS(AMAX_WIND-BMAX_WIND)_rank.png', 'ABS(AMAX_WIND-BMAX_WIND)_relperf.png', - 'ABS(AMAX_WIND-BMAX_WIND)_skill_md.png', - 'ABS(AMAX_WIND-BMAX_WIND)_skill_mn.png', - 'TK_ERR_boxplot.png', 'TK_ERR_mean.png', 'TK_ERR_median.png', 'TK_ERR_rank.png', - 'TK_ERR_relperf.png', 'TK_ERR_skill_md.png', 'TK_ERR_skill_mn.png'] - - for cur_file in only_files: - assert cur_file in expected_names_list - - # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # ONLY RUN THESE TESTS ON A LOCAL MACHINE, they are not reliable when run in Github Actions container - #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - # Check that file sizes (in bytes) are consistent (cannot check rank files, the confidence - # intervals are generated using random seed-they will not be identical from one run to another - # for cur_file in only_files: - # match = re.match(r'.+_(mean).png', cur_file) - # if match: - # cur_file_size = int(os.path.getsize(os.path.join(output_dir, cur_file))) - # expected_size = int(expected_sizes[cur_file]) - # assert cur_file_size >= expected_size - # - - # Clean up - try: - for cur_file in only_files: - os.remove(os.path.join(output_dir, cur_file)) - os.rmdir(output_dir) - except FileNotFoundError: - # If files already cleaned up, then ignore error - pass +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("tcmpr_multi.yaml", [ + 'multi/ABS(AMAX_WIND-BMAX_WIND)_boxplot.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_mean.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_median.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_rank.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_relperf.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_skill_md.png', + 'multi/ABS(AMAX_WIND-BMAX_WIND)_skill_mn.png', + 'multi/TK_ERR_boxplot.png', + 'multi/TK_ERR_mean.png', + 'multi/TK_ERR_median.png', + 'multi/TK_ERR_rank.png', # note the rank file will differ each run due to the random seed + 'multi/TK_ERR_relperf.png', + 'multi/TK_ERR_skill_md.png', + 'multi/TK_ERR_skill_mn.png', + ]), + ("tcmpr_point_tcdiag.yaml", [ + "point_tcdiag/AMAX_WIND-BMAX_WIND_pointplot.png", + "point_tcdiag/SHEAR_MAGNITUDE_pointplot.png", + ]), +]) +def test_tcmpr_plots(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the tcmpr plots are getting created""" + + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + tcmpr.main(f"{os.environ['TEST_DIR']}/{input_yaml}") + + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/wind_rose/minimal_wind_rose.yaml b/test/wind_rose/minimal_wind_rose.yaml index 76db4667..d8456d73 100644 --- a/test/wind_rose/minimal_wind_rose.yaml +++ b/test/wind_rose/minimal_wind_rose.yaml @@ -2,10 +2,6 @@ # **NOTE**: update the stat_input and plot_filename settings with the full path to your # input file and output file, respectively. stat_input: !ENV '${TEST_DIR}/point_stat_mpr.txt' -plot_filename: !ENV '${TEST_DIR}/wind_rose_default.png' +plot_filename: !ENV '${TEST_OUTPUT}/wind_rose_minimal.png' -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -#points_path: '/dir_to_save_points1_file' \ No newline at end of file +points_path: !ENV '${TEST_OUTPUT}/minimal' \ No newline at end of file diff --git a/test/wind_rose/test_wind_rose.py b/test/wind_rose/test_wind_rose.py index 04ac6284..e6a7628a 100644 --- a/test/wind_rose/test_wind_rose.py +++ b/test/wind_rose/test_wind_rose.py @@ -1,102 +1,29 @@ import pytest import os from metplotpy.plots.wind_rose import wind_rose -#from metcalcpy.compare_images import CompareImages cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['wind_rose_custom.png', 'point_stat_mpr.points1'] -@pytest.fixture -def setup(remove_files, setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - setup_env(cwd) - custom_config_filename = f"{cwd}/wind_rose_custom.yaml" - - # Invoke the command to generate a Wind rose Diagram based on - # a custom config file. - wind_rose.main(custom_config_filename) - - -def cleanup(): - # remove the .png and .points files - # from any previous runs - try: - plot_file = 'wind_rose_custom.png' - points_file_1 = 'point_stat_mpr.points1' - os.remove(os.path.join(cwd, plot_file)) - os.remove(os.path.join(cwd, points_file_1)) - except OSError: - pass - - -@pytest.mark.parametrize("test_input,expected", - (["wind_rose_expected.png", True], ["wind_rose_expected.points", True])) -def test_files_exist(setup, test_input, expected, remove_files): - ''' - Checking that the plot and data files are getting created - ''' - assert os.path.isfile(f"{cwd}/{test_input}") == expected - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input,expected", - (["intermed_files/wind_rose_custom_points.png", True], ["intermed_files/point_stat_mpr.points1", True])) -def test_points1_files_exist(setup_env, test_input, expected, remove_files): - ''' - Checking that the plot file and points1 file are getting created where expected - plot and point file are being saved in the intermed_files subdir - ''' - setup_env(cwd) - custom_config_filename = f"{cwd}/wind_rose_custom_points.yaml" - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Wind Rose Diagram based on - # a custom config file. - wind_rose.main(custom_config_filename) - - assert os.path.isfile(f"{cwd}/{test_input}") == expected - - # remove the plot and points1 files that were created - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.parametrize("test_input,expected", - (["intermed_files/wind_rose_points2.png", True], - ["intermed_files/point_stat_mpr.points1", True])) -def test_points1_files_exist(setup_env, test_input, expected, remove_files): - ''' - Checking that the plot file and points1 file are getting created where expected - plot and point file are being saved in the intermed_files subdir. Verify that when - no stat_file is specified, the point_stat_mpr.txt in the test dir is being used. - ''' - setup_env(cwd) - custom_config_filename = f"{cwd}/wind_rose_custom2_points.yaml" - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Wind Rose Diagram based on - # a custom config file. - wind_rose.main(custom_config_filename) - - assert os.path.isfile(f"{cwd}/{test_input}") == expected - - # remove the plot and points1 files and intermed_files that were created - remove_files(cwd, CLEANUP_FILES) - - -@pytest.mark.skip("unreliable sometimes fails due to differences in machines.") -def test_images_match(setup, remove_files): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - comparison = CompareImages(f'{cwd}/wind_rose_expected.png', f'{cwd}/wind_rose_custom.png') - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("wind_rose_custom.yaml", [ + "wind_rose_custom.png", + "custom/point_stat_mpr.points1", + ]), + ("wind_rose_custom_points.yaml", [ + "wind_rose_custom_points.png", + "custom_points/point_stat_mpr.points1", + ]), + ("minimal_wind_rose.yaml", [ + "wind_rose_minimal.png", + "minimal/point_stat_mpr.points1", + ]), +]) +def test_wind_rose(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plots are being created""" + + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + wind_rose.main(f"{cwd}/{input_yaml}") + + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/wind_rose/wind_rose_custom.yaml b/test/wind_rose/wind_rose_custom.yaml index 605a8792..c046047a 100644 --- a/test/wind_rose/wind_rose_custom.yaml +++ b/test/wind_rose/wind_rose_custom.yaml @@ -1,3 +1,10 @@ +plot_filename: !ENV '${TEST_OUTPUT}/wind_rose_custom.png' + +dump_points: True +points_path: !ENV '${TEST_OUTPUT}/custom' + +stat_input: !ENV '${TEST_DIR}/point_stat_mpr.txt' + type: FCST title: Wind Rose wind_rose_breaks: @@ -30,19 +37,6 @@ angularaxis_ticktext: - 'S' - 'W' -# !!!!!!!! IMPORTANT !!!!!! -# Uncomment the stat_input setting and specify the full path to the -# point_stat_mpr.txt file -stat_input: !ENV '${TEST_DIR}/point_stat_mpr.txt' - -plot_filename: !ENV '${TEST_DIR}/wind_rose_custom.png' -dump_points: True - -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -# points_path: '/dir_to_save_points1_file' show_in_browser: False # To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have diff --git a/test/wind_rose/wind_rose_custom2_points.yaml b/test/wind_rose/wind_rose_custom2_points.yaml deleted file mode 100644 index 544405ff..00000000 --- a/test/wind_rose/wind_rose_custom2_points.yaml +++ /dev/null @@ -1,36 +0,0 @@ -type: FCST -title: Wind Rose -wind_rose_breaks: - - 0.0 - - 1.0 - - 2.0 - - 3.0 - - 4.0 - - 5.0 - - 6.0 -wind_rose_angle: 30 -wind_rose_marker_colors: - - 'rgb(95,78,160)' - - 'rgb(78,176,170)' - - 'rgb(192,228,160)' - - 'rgb(255,255,194)' - - 'rgb(253,193,115)' - - 'rgb(236,94,74)' - - 'rgb(159,11,68)' -create_figure: True -show_legend: True -angularaxis_tickvals: - - 0 - - 90 - - 180 - - 270 -angularaxis_ticktext: - - 'N' - - 'E' - - 'S' - - 'W' -plot_filename: !ENV '${TEST_DIR}/intermed_files/wind_rose_points2.png' -dump_points: True -points_path: !ENV '${TEST_DIR}/intermed_files' -# No stat_input, force creation of 'default' points file name -show_in_browser: False \ No newline at end of file diff --git a/test/wind_rose/wind_rose_custom_points.yaml b/test/wind_rose/wind_rose_custom_points.yaml index 56c1347c..7f54d674 100644 --- a/test/wind_rose/wind_rose_custom_points.yaml +++ b/test/wind_rose/wind_rose_custom_points.yaml @@ -1,3 +1,12 @@ +plot_filename: !ENV '${TEST_OUTPUT}/wind_rose_custom_points.png' + +dump_points: True +points_path: !ENV '${TEST_OUTPUT}/custom_points' + +# No stat_input, force creation of 'default' points file name + +show_in_browser: False + type: FCST title: Wind Rose wind_rose_breaks: @@ -29,8 +38,3 @@ angularaxis_ticktext: - 'E' - 'S' - 'W' -plot_filename: !ENV '${TEST_DIR}/intermed_files/wind_rose_custom_points.png' -dump_points: True -points_path: !ENV '${TEST_DIR}/intermed_files' -stat_input: !ENV '${TEST_DIR}/point_stat_mpr.txt' -show_in_browser: False \ No newline at end of file From 45d3e99500bc22d23deb7aff20f03829be4bde1d Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:09:28 -0700 Subject: [PATCH 18/31] create directory containing output image if it does not already exist. change print statements to error logging --- metplotpy/plots/wind_rose/wind_rose.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/metplotpy/plots/wind_rose/wind_rose.py b/metplotpy/plots/wind_rose/wind_rose.py index fea5d568..34e4c1f1 100644 --- a/metplotpy/plots/wind_rose/wind_rose.py +++ b/metplotpy/plots/wind_rose/wind_rose.py @@ -317,14 +317,15 @@ def save_to_file(self) -> None: image_name = self.get_config_value('plot_filename') if self.figure: try: + os.makedirs(os.path.dirname(image_name), exist_ok=True) self.figure.write_image(image_name) except FileNotFoundError: - print("Can't save to file " + image_name) + self.logger.error("Can't save to file " + image_name) except ValueError as ex: - print(ex) + self.logger.error(ex) else: - print("Oops! The figure was not created. Can't save.") + self.logger.error("Oops! The figure was not created. Can't save.") @staticmethod def _boundary_filter(boundary_lower_speed: float, From 17ef868b9a9af98fec5d907eb8446895d097290e Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:33:07 -0700 Subject: [PATCH 19/31] fix bug in logic to read skew_t data --- metplotpy/plots/skew_t/skew_t.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metplotpy/plots/skew_t/skew_t.py b/metplotpy/plots/skew_t/skew_t.py index 3861edaf..4bce8b15 100644 --- a/metplotpy/plots/skew_t/skew_t.py +++ b/metplotpy/plots/skew_t/skew_t.py @@ -66,7 +66,7 @@ def extract_sounding_data(input_file, output_directory): txt_file.write("".join(line) + "\n") # Read in the current sounding data file, replacing any 9999 values with NaN. - df_raw: pandas.DataFrame = pd.read_csv(sounding_data_file, sep='/s+', skiprows=1, engine='python') + df_raw: pandas.DataFrame = pd.read_csv(sounding_data_file, sep='\s+', skiprows=1, engine='python') df_raw.replace('9999', 'NA', inplace=True) # Rename some columns so they are more descriptive From 2ed2e595f7c8caf5d4712647b7c2af21f254fd46 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:33:38 -0700 Subject: [PATCH 20/31] commit PyCharm files to avoid seeing diffs -- will resolve this later --- .idea/METplotpy.iml | 2 +- .idea/misc.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/METplotpy.iml b/.idea/METplotpy.iml index a2a889b2..d5edc058 100644 --- a/.idea/METplotpy.iml +++ b/.idea/METplotpy.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index b84ee64d..23fec60a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file From 1e008179e71cc78dbf5b3a54defb6df4e972616f Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:34:38 -0700 Subject: [PATCH 21/31] create directory for output file --- metplotpy/plots/taylor_diagram/taylor_diagram.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metplotpy/plots/taylor_diagram/taylor_diagram.py b/metplotpy/plots/taylor_diagram/taylor_diagram.py index 904b64df..b6b7bc54 100644 --- a/metplotpy/plots/taylor_diagram/taylor_diagram.py +++ b/metplotpy/plots/taylor_diagram/taylor_diagram.py @@ -19,6 +19,7 @@ """ __author__ = 'Minna Win' +import os import warnings from datetime import datetime import matplotlib.pyplot as plt @@ -333,6 +334,7 @@ def _create_figure(self) -> None: # Save the figure, based on whether we are displaying only positive # correlations or all # correlations. + os.makedirs(os.path.dirname(self.config_obj.output_image), exist_ok=True) if pos_correlation_only: # Setting the bbox_inches keeps the legend box always within the plot # boundaries. This *may* result From 3820b3da9a809b4072ec606e86767a93418e05df Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:34:56 -0700 Subject: [PATCH 22/31] remove accidental slash --- metplotpy/plots/tcmpr_plots/tcmpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metplotpy/plots/tcmpr_plots/tcmpr.py b/metplotpy/plots/tcmpr_plots/tcmpr.py index 9988e064..41ddfb95 100755 --- a/metplotpy/plots/tcmpr_plots/tcmpr.py +++ b/metplotpy/plots/tcmpr_plots/tcmpr.py @@ -585,7 +585,7 @@ def create_plot(config_obj: dict) -> None: quotechar='"', skipinitialspace=True, encoding='utf-8') logger = util.get_common_logger(config_obj.log_level, config_obj.log_filename) -\ + for plot_type in config_obj.plot_type_list: # Apply event equalization, if requested From cd465935cf67a5524ad8e85374b57d6ac169346f Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:38:16 -0700 Subject: [PATCH 23/31] create test_output directory under wherever the top-level test output directory is defined (METplotpy/test or METPLOTPY_TEST_OUTPUT if set) so we can more safely remove the files in the test output directory without risking wiping out user files if METPLOTPY_TEST_OUTPUT is set incorrectly --- test/conftest.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 2aa40606..2722d95a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -18,7 +18,7 @@ # This fixture temporarily sets the working directory # to the dir containing the test file. This means -# realative file locations can be used for each test +# relative file locations can be used for each test # file. # NOTE: autouse=True means this applies to ALL tests. # Code that updates the cwd inside test is now redundant @@ -91,9 +91,9 @@ def set_environ(test_dir): # write test output under METPLOTPY_TEST_OUTPUT if set, # otherwise write to test/test_output/ output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', - os.path.join(test_dir, os.pardir, 'test_output')) + os.path.join(test_dir, os.pardir)) # write to a subdirectory named after the plot type - os.environ['TEST_OUTPUT'] = os.path.join(output_dir, os.path.basename(test_dir)) + os.environ['TEST_OUTPUT'] = os.path.join(output_dir, 'test_output', os.path.basename(test_dir)) return set_environ @@ -102,18 +102,22 @@ def set_environ(test_dir): def module_setup_env(request): """Module-scoped fixture that sets up environment variables once per test module. - This fixture automatically determines the test directory from the requesting - test module's location. + This fixture automatically determines the test directory from the test module's location. """ test_dir = request.fspath.dirname print("Setting up environment") os.environ['TEST_DIR'] = test_dir - # write test output under METPLOTPY_TEST_OUTPUT if set, - # otherwise write to test/test_output/ - output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', - os.path.join(test_dir, os.pardir, 'test_output')) + # write test output under METPLOTPY_TEST_OUTPUT if set, otherwise write to test/test_output # write to a subdirectory named after the plot type - os.environ['TEST_OUTPUT'] = os.path.join(output_dir, os.path.basename(test_dir)) + output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', os.path.join(test_dir, os.pardir)) + output_dir = os.path.join(output_dir, 'test_output', os.path.basename(test_dir)) + + # remove output directory for plot type if it already exists to ensure clean test environment + if os.path.exists(output_dir): + print(f"Removing existing output directory: {output_dir}") + shutil.rmtree(output_dir) + + os.environ['TEST_OUTPUT'] = output_dir yield # Optional: cleanup after all tests in the module complete From 6c7d0ecef51b7568757dd8452969a41ca0b625b8 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:39:05 -0700 Subject: [PATCH 24/31] update scatter, skew_t, and taylor_diagram tests to write to output directory --- test/scatter/test_scatter.py | 48 +++----- test/scatter/test_scatter_mpr.yaml | 11 +- test/skew_t/test_skew_t.py | 26 +---- test/skew_t/test_skew_t.yaml | 4 +- .../taylor_diagram/taylor_diagram_custom.yaml | 2 +- .../taylor_diagram/test_neg_and_pos_corr.yaml | 2 +- test/taylor_diagram/test_pos_corr.yaml | 2 +- test/taylor_diagram/test_taylor_diagram.py | 108 +++--------------- 8 files changed, 47 insertions(+), 156 deletions(-) diff --git a/test/scatter/test_scatter.py b/test/scatter/test_scatter.py index aee0593f..f4d963f2 100644 --- a/test/scatter/test_scatter.py +++ b/test/scatter/test_scatter.py @@ -1,29 +1,14 @@ import os import math -import shutil import pandas as pd -import yaml -from metplotpy.plots.scatter import scatter as sc + +from metplotpy.plots.scatter import scatter as scatter """ Test for the scatter plot """ -def read_config(config_filename) -> dict: - """ - Args: - @param config_filename: The name of the YAML config file - - Returns: - parms: a dictionary representation of the YAML config file - """ - with open(config_filename, 'r') as stream: - try: - parms = yaml.load(stream, Loader=yaml.FullLoader) - return parms - except yaml.YAMLError as exc: - print(exc) -def test_files_exist(): +def test_scatter(module_setup_env, remove_files): """ Generate a scatter plot from reformatted MPR Usecase data and check that the plot file is created, the dump points file is created @@ -31,19 +16,20 @@ def test_files_exist(): first row of data). Clean up the output directory when finished. """ - os.environ['METPLOTPY_BASE'] = "../../" - test_config_filename = os.path.join(os.getcwd(), "test_scatter_mpr.yaml") - sc.main(test_config_filename) + # note: scatter_log.txt does not appear to be created + expected_files = [ + "scatter_mpr_tmp_obs_lat.png", + "plot_points.txt", + ] + remove_files(os.environ['TEST_OUTPUT'], expected_files) + + scatter.main(f"{os.environ['TEST_DIR']}/test_scatter_mpr.yaml") - # Verify that the plot was generated - plot_file = "scatter_mpr_tmp_obs_lat.png" - path = os.path.join(os.getcwd(), 'output') - fullpath = os.path.join(path, plot_file) - assert os.path.isfile(fullpath) == True + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") - # Verify that the dump point file, plot_points.txt was generated and has expected points - dump_points_file = os.path.join(path, 'plot_points.txt') - assert os.path.isfile(dump_points_file) == True + # Verify that the dump point file, plot_points.txt has expected points + dump_points_file = os.path.join(os.environ['TEST_OUTPUT'], 'plot_points.txt') df = pd.read_csv(dump_points_file, sep='\t', skiprows=0) # expected x, y, and z values for the first row @@ -53,7 +39,3 @@ def test_files_exist(): assert math.isclose( df.iloc[0,0], expected_x ) assert math.isclose(df.iloc[0,1], expected_y) assert math.isclose(df.iloc[0,2], expected_z) - - # clean up files in the output directory and - # the output directory - shutil.rmtree(os.path.join(os.getcwd(), 'output')) diff --git a/test/scatter/test_scatter_mpr.yaml b/test/scatter/test_scatter_mpr.yaml index f436cbca..18f6ad38 100644 --- a/test/scatter/test_scatter_mpr.yaml +++ b/test/scatter/test_scatter_mpr.yaml @@ -1,8 +1,14 @@ +plot_filename: !ENV '${TEST_OUTPUT}/scatter_mpr_tmp_obs_lat.png' +points_path: !ENV '${TEST_OUTPUT}' +log_filename: !ENV '${TEST_OUTPUT}/scatter_log.txt' + +stat_input: !ENV '${TEST_DIR}/reformatted_data_for_scatter.data' + + caption_align: 0.4 caption_offset: 2.88 caption_size: 6 dump_points: True -points_path: ./output/ var_val_x_axis: fcst var_val_y_axis: obs fcst_var: TMP @@ -18,7 +24,6 @@ indy_stagger: False legend_box: o legend_ncol: 3 legend_size: 0.8 -log_filename: './output/scatter_log.txt' log_level: INFO # Supported symbols: ., o, ^, d, H, or s (^= triangle, d=diamond, H=hexagon, s=square) marker_symbol: . @@ -26,13 +31,11 @@ marker_symbol: . marker_colormap: 'nipy_spectral' marker_size: 6 plot_caption: 'optional caption' -plot_filename: ./output/scatter_mpr_tmp_obs_lat.png plot_height: 8.5 plot_width: 11.0 show_legend: True show_trendline: True start_from_zero: False -stat_input: ./reformatted_data_for_scatter.data title: "MPR usecase data for TMP, P900-750" title_align: 0.5 title_offset: -2 diff --git a/test/skew_t/test_skew_t.py b/test/skew_t/test_skew_t.py index 42e003ec..87853706 100644 --- a/test/skew_t/test_skew_t.py +++ b/test/skew_t/test_skew_t.py @@ -1,25 +1,17 @@ -import os import pytest -import shutil + +import os import re from metplotpy.plots.skew_t import skew_t as skew_t -# from metcalcpy.compare_images import CompareImages - -cwd = os.path.dirname(__file__) - -def test_skew_t(setup_env): - setup_env(cwd) - custom_config_filename = os.path.join(cwd, "test_skew_t.yaml") - - # Invoke the command to generate a skew-T Diagram based on - # the test_skew_tm.yaml custom config file. +def test_skew_t(module_setup_env): + custom_config_filename = os.path.join(os.environ['TEST_DIR'], "test_skew_t.yaml") skew_t.main(custom_config_filename) # Verify that files for the ssh052023 data exists for the 0,6, 12,18,24, 30, 42, # 48, 54, and 60 hour data. - output_dir = os.path.join(cwd, 'output') + output_dir = os.environ['TEST_OUTPUT'] # Some of these data files have incomplete data so check for the expected hour # plots. @@ -39,12 +31,6 @@ def test_skew_t(setup_env): _check_files_not_created(files_of_interest) _check_empty_input(files_of_interest) - # Clean up all png files - shutil.rmtree(output_dir) - # If running without the ' -p no:logging' option, then uncomment to ensure that log - # files are removed. - # shutil.rmtree('./logs') - def _check_files_exist(files_of_interest): ''' @@ -80,7 +66,7 @@ def _check_files_exist(files_of_interest): if expected in subset_files_of_interest: num_found += 1 - assert len(subset_files_of_interest) == num_found + assert len(expected_base_filenames) == num_found def _check_files_not_created(files_of_interest): diff --git a/test/skew_t/test_skew_t.yaml b/test/skew_t/test_skew_t.yaml index b0d0759b..202b5a61 100644 --- a/test/skew_t/test_skew_t.yaml +++ b/test/skew_t/test_skew_t.yaml @@ -6,9 +6,9 @@ # Input and output file information input_directory: !ENV '${TEST_DIR}/data/2023/sh052023' input_file_extension: '.dat' -output_directory: !ENV '${TEST_DIR}/output' +output_directory: !ENV '${TEST_OUTPUT}' log_level: "INFO" -log_directory: !ENV '${TEST_DIR}/logs' +log_directory: !ENV '${TEST_OUTPUT}/logs' log_filename: 'tc_diags_skewt.log' # Sounding hours of interest. If all_sounding_hours is set to False, then the diff --git a/test/taylor_diagram/taylor_diagram_custom.yaml b/test/taylor_diagram/taylor_diagram_custom.yaml index 26fce36e..6f9886f3 100644 --- a/test/taylor_diagram/taylor_diagram_custom.yaml +++ b/test/taylor_diagram/taylor_diagram_custom.yaml @@ -2,7 +2,7 @@ # custom config file to override some of the settings in the # default config file taylor_diagram_defaults.yaml stat_input: !ENV '${TEST_DIR}/plot_dlwr_sample.data' -plot_filename: !ENV '${TEST_DIR}/taylor_diagram_custom.png' +plot_filename: !ENV '${TEST_OUTPUT}/taylor_diagram_custom.png' # change the caption text plot_caption: "Custom caption" diff --git a/test/taylor_diagram/test_neg_and_pos_corr.yaml b/test/taylor_diagram/test_neg_and_pos_corr.yaml index 454f1120..b638a3d7 100644 --- a/test/taylor_diagram/test_neg_and_pos_corr.yaml +++ b/test/taylor_diagram/test_neg_and_pos_corr.yaml @@ -2,7 +2,7 @@ # custom config file to override some of the settings in the # default config file taylor_diagram_defaults.yaml stat_input: !ENV '${TEST_DIR}/plot_dlwr_sample.data' -plot_filename: !ENV '${TEST_DIR}/test_neg_and_pos_corr_plot.png' +plot_filename: !ENV '${TEST_OUTPUT}/test_neg_and_pos_corr_plot.png' taylor_show_gamma: True # Show only positive values of correlation taylor_voc: False diff --git a/test/taylor_diagram/test_pos_corr.yaml b/test/taylor_diagram/test_pos_corr.yaml index 27c995fb..c51dd571 100644 --- a/test/taylor_diagram/test_pos_corr.yaml +++ b/test/taylor_diagram/test_pos_corr.yaml @@ -2,7 +2,7 @@ # custom config file to override some of the settings in the # default config file taylor_diagram_defaults.yaml stat_input: !ENV '${TEST_DIR}/plot_dlwr_sample.data' -plot_filename: !ENV '${TEST_DIR}/test_pos_corr_plot.png' +plot_filename: !ENV '${TEST_OUTPUT}/test_pos_corr_plot.png' taylor_show_gamma: True # Show only positive values of correlation taylor_voc: True \ No newline at end of file diff --git a/test/taylor_diagram/test_taylor_diagram.py b/test/taylor_diagram/test_taylor_diagram.py index ab4001ac..f345d59f 100644 --- a/test/taylor_diagram/test_taylor_diagram.py +++ b/test/taylor_diagram/test_taylor_diagram.py @@ -1,105 +1,25 @@ -import os -from metplotpy.plots.taylor_diagram import taylor_diagram as td import pytest -#from metcalcpy.compare_images import CompareImages -cwd = os.path.dirname(__file__) +import os + +from metplotpy.plots.taylor_diagram import taylor_diagram as taylor_diagram # Converts MatplotlibDeprecation warnings (which are DeprecationWarning) into errors as # any DeprecationWarnings should be fixed as soon as possible pytestmark = pytest.mark.filterwarnings("error::DeprecationWarning") -def test_pos_corr_file_exists(setup_env): - setup_env(cwd) - - test_config_filename = f"{cwd}/test_pos_corr.yaml" - td.main(test_config_filename) - - # Verify that a plot was generated - plot_file = f"{cwd}/test_pos_corr_plot.png" - expected_file = f"{cwd}/expected_pos_corr_plot.png" - assert os.path.isfile(plot_file) - - # image comparison - #comparison = CompareImages(plot_file,expected_file) - # assert comparison.mssim >= .99 - - # Clean up - os.remove(plot_file) - - -def test_pos_corr_file_exists(setup_env): - setup_env(cwd) - test_config_filename = f"{cwd}/test_pos_corr.yaml" - td.main(test_config_filename) - - # Verify that a plot was generated - plot_file = f"{cwd}/test_pos_corr_plot.png" - assert os.path.isfile(plot_file) - - # Clean up - os.remove(plot_file) - -# Not reliable when the expected image is generated on a Mac and then this -# test is run on non-Mac machine. -# def test_pos_corr_images_match(): -# os.environ['METPLOTPY_BASE'] = "../../metplotpy" -# test_config_filename = "test_pos_corr.yaml" -# td.main(test_config_filename) -# -# # Verify that a plot was generated -# plot_file = "test_pos_corr_plot.png" -# expected_file = "expected_pos_corr_plot.png" -# path = os.getcwd() -# -# # image comparison -# comparison = CompareImages(plot_file, expected_file) -# assert comparison.mssim >= .99 -# -# # Clean up -# os.remove(os.path.join(path, plot_file)) - - -def test_neg_and_pos_corr_file_exists(setup_env): - setup_env(cwd) - test_config_filename = f"{cwd}/test_neg_and_pos_corr.yaml" - td.main(test_config_filename) - - # Verify that a plot was generated - plot_file = f"{cwd}/test_neg_and_pos_corr_plot.png" - assert os.path.isfile(plot_file) - - # Clean up - os.remove(plot_file) - - -# Not reliable when the expected image is generated on a Mac and then this -# test is run on non-Mac machine. -def test_neg_and_pos_corr_images_match(setup_env): - setup_env(cwd) - test_config_filename = f"{cwd}/test_neg_and_pos_corr.yaml" - td.main(test_config_filename) - - # Verify that a plot was generated - plot_file = f"{cwd}/test_neg_and_pos_corr_plot.png" - expected_file = f"{cwd}/expected_neg_and_pos_corr_plot.png" - - # image comparison, with allowance of .99 match instead of 100% match - #comparison = CompareImages(plot_file, expected_file) - #assert comparison.mssim >= .99 - - # Clean up - os.remove(plot_file) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("test_pos_corr.yaml", ["test_pos_corr_plot.png"]), + ("test_neg_and_pos_corr.yaml", ["test_neg_and_pos_corr_plot.png"]), + ("taylor_diagram_custom.yaml", ["taylor_diagram_custom.png"]), +]) +def test_taylor_diagram(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" -def test_custom_plot_exists(setup_env): - setup_env(cwd) - test_config_filename = f"{cwd}/taylor_diagram_custom.yaml" - td.main(test_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) - # Verify that a plot was generated - plot_file = f"{cwd}/taylor_diagram_custom.png" - assert os.path.isfile(plot_file) + taylor_diagram.main(f"{os.environ['TEST_DIR']}/{input_yaml}") - # Clean up - os.remove(plot_file) + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") From 923e01ad7d2bf8d264e0e323651ef2d3f192cf9b Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:50:25 -0700 Subject: [PATCH 25/31] revise performance_diagram tests to write to output directory --- .../custom_performance_diagram.yaml | 21 ++-- ...om_performance_diagram_defaultpoints1.yaml | 107 ----------------- .../custom_performance_diagram_points1.yaml | 104 ---------------- .../test_performance_diagram.py | 112 ++---------------- 4 files changed, 20 insertions(+), 324 deletions(-) delete mode 100644 test/performance_diagram/custom_performance_diagram_defaultpoints1.yaml delete mode 100644 test/performance_diagram/custom_performance_diagram_points1.yaml diff --git a/test/performance_diagram/custom_performance_diagram.yaml b/test/performance_diagram/custom_performance_diagram.yaml index 6527cb1e..e013d446 100644 --- a/test/performance_diagram/custom_performance_diagram.yaml +++ b/test/performance_diagram/custom_performance_diagram.yaml @@ -1,8 +1,11 @@ --- -# Custom settings specific to performance diagram plot -# Write points file. Set to True for METviewer use, -# False otherwise -dump_points_1: 'False' +plot_filename: !ENV '${TEST_OUTPUT}/performance_diagram_actual.png' + +points_path: !ENV '${TEST_OUTPUT}/intermed_files' +dump_points_1: 'True' + +stat_input: !ENV '${TEST_DIR}/plot_20200317_151252.data' + # Optional, uncomment and set to directory to store the .points1 file # that is used by METviewer (created when dump_points_1 is set to True) @@ -104,14 +107,4 @@ series_line_style: - "--" # dotted line - ":" -stat_input: !ENV '${TEST_DIR}/plot_20200317_151252.data' -plot_filename: !ENV '${TEST_DIR}/performance_diagram_actual.png' event_equal: False - -# To save your log output to a file, specify a path and filename and uncomment the line below. Make sure you have -# permissions to the directory you specify. The default, as specified in the default config file is stdout. -#log_filename: ./performance_diagram.log - -# To change the log level, specify a log level: debug, info, warning, error and uncomment the line below. -# Debug and info log level will produce more log output. -#log_level: WARNING diff --git a/test/performance_diagram/custom_performance_diagram_defaultpoints1.yaml b/test/performance_diagram/custom_performance_diagram_defaultpoints1.yaml deleted file mode 100644 index d6e5480f..00000000 --- a/test/performance_diagram/custom_performance_diagram_defaultpoints1.yaml +++ /dev/null @@ -1,107 +0,0 @@ ---- -# Custom settings specific to performance diagram plot -# Line and marker plots of series data of Success Rate (1-FAR) on x-axis vs. PODY on y-axis. -title: Performance Diagram ("Custom title") -xaxis: Success Ratio - -dump_points_1: 'True' - -# Do not set the points_path so that the points1 path is saved in the same directory as the -# input data. -#points_path: './intermed_files' - -# support two y-axes -yaxis_1: Probability of Detection (PODY) -yaxis_2: '' -legend_ncol: 1 -plot_disp: - - True - - True - - True -series_order: - - 2 - - 1 - - 3 -indy_var: fcst_valid_beg -indy_vals: - # defining the datetimes of interest - - 2016-08-12 06:00:00 - - 2016-08-12 12:00:00 - - 2016-08-12 18:00:00 - - 2016-08-13 00:00:00 - - 2016-08-13 06:00:00 - - 2016-08-13 12:00:00 - - 2016-08-13 18:00:00 - - 2016-08-14 00:00:00 - - 2016-08-14 06:00:00 - - 2016-08-14 12:00:00 - - 2016-08-14 18:00:00 - - 2016-08-15 00:00:00 - - 2016-08-15 12:00:00 - - 2016-08-15 18:00:00 - - 2016-08-16 00:00:00 - - 2016-08-16 06:00:00 - - 2016-08-16 12:00:00 - - 2016-08-16 18:00:00 - - 2016-08-17 00:00:00 - -# support two series values -series_val_1: - model: - - GFS_0p25_G193 - vx_mask: - - NH_CMORPH_G193 - - SH_CMORPH_G193 - - TROP_CMORPH_G193 -series_val_2: {} -fcst_var_val_1: - # Name of the variables of interest, the independent and dependent variables - # will be the same across all fcst_var values - - # fcst_var1 - APCP_06: - # x-axis/independent variable - - FAR - # y-axis/dependent variable - - PODY -fcst_var_val_2: -user_legend: -# - NH -# - SH -# - TROP - - NH - - '' - - TROP - -plot_stat: median -colors: - # red - - "#ff0000" - # limegreen, as name of color - - limegreen - # electric indigo/vivid purple - - "#8000ff" -series_line_width: - - 1 - - 1 - - NA -series_symbols: - # Other supported symbols: small circle = ".", ring/hexagon = "H", rhombus/diamond="d" - # Circle - - "o" - # square - - "s" - # triangle - #- "^" - # ring - - "H" -series_line_style: - # solid line - - "-" - # dashed line - - "--" - # dotted line - - ":" -stat_input: !ENV '${TEST_DIR}/plot_20200317_151252.data' -plot_filename: !ENV '${TEST_DIR}/performance_diagram_defaultpoints1.png' -event_equalize: False diff --git a/test/performance_diagram/custom_performance_diagram_points1.yaml b/test/performance_diagram/custom_performance_diagram_points1.yaml deleted file mode 100644 index aaf20796..00000000 --- a/test/performance_diagram/custom_performance_diagram_points1.yaml +++ /dev/null @@ -1,104 +0,0 @@ ---- -# Custom settings specific to performance diagram plot -# Line and marker plots of series data of Success Rate (1-FAR) on x-axis vs. PODY on y-axis. -title: Performance Diagram ("Custom title") -xaxis: Success Ratio - -dump_points_1: 'True' -points_path: !ENV '${TEST_DIR}/intermed_files' - -# support two y-axes -yaxis_1: Probability of Detection (PODY) -yaxis_2: '' -legend_ncol: 1 -plot_disp: - - True - - True - - True -series_order: - - 2 - - 1 - - 3 -indy_var: fcst_valid_beg -indy_vals: - # defining the datetimes of interest - - 2016-08-12 06:00:00 - - 2016-08-12 12:00:00 - - 2016-08-12 18:00:00 - - 2016-08-13 00:00:00 - - 2016-08-13 06:00:00 - - 2016-08-13 12:00:00 - - 2016-08-13 18:00:00 - - 2016-08-14 00:00:00 - - 2016-08-14 06:00:00 - - 2016-08-14 12:00:00 - - 2016-08-14 18:00:00 - - 2016-08-15 00:00:00 - - 2016-08-15 12:00:00 - - 2016-08-15 18:00:00 - - 2016-08-16 00:00:00 - - 2016-08-16 06:00:00 - - 2016-08-16 12:00:00 - - 2016-08-16 18:00:00 - - 2016-08-17 00:00:00 - -# support two series values -series_val_1: - model: - - GFS_0p25_G193 - vx_mask: - - NH_CMORPH_G193 - - SH_CMORPH_G193 - - TROP_CMORPH_G193 -series_val_2: {} -fcst_var_val_1: - # Name of the variables of interest, the independent and dependent variables - # will be the same across all fcst_var values - - # fcst_var1 - APCP_06: - # x-axis/independent variable - - FAR - # y-axis/dependent variable - - PODY -fcst_var_val_2: -user_legend: -# - NH -# - SH -# - TROP - - NH - - '' - - TROP - -plot_stat: median -colors: - # red - - "#ff0000" - # limegreen, as name of color - - limegreen - # electric indigo/vivid purple - - "#8000ff" -series_line_width: - - 1 - - 1 - - NA -series_symbols: - # Other supported symbols: small circle = ".", ring/hexagon = "H", rhombus/diamond="d" - # Circle - - "o" - # square - - "s" - # triangle - #- "^" - # ring - - "H" -series_line_style: - # solid line - - "-" - # dashed line - - "--" - # dotted line - - ":" -stat_input: !ENV '${TEST_DIR}/plot_20200317_151252.data' -plot_filename: !ENV '${TEST_DIR}/performance_diagram_actual_points1.png' -event_equalize: False diff --git a/test/performance_diagram/test_performance_diagram.py b/test/performance_diagram/test_performance_diagram.py index 511e8a8d..cc1dbf7f 100644 --- a/test/performance_diagram/test_performance_diagram.py +++ b/test/performance_diagram/test_performance_diagram.py @@ -1,106 +1,20 @@ import os import pytest -from metplotpy.plots.performance_diagram import performance_diagram as pd -#from metcalcpy.compare_images import CompareImages +from metplotpy.plots.performance_diagram import performance_diagram as performance_diagram -cwd = os.path.dirname(__file__) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_performance_diagram.yaml", [ + "performance_diagram_actual.png", + "intermed_files/plot_20200317_151252.points1", + ]), +]) +def test_performance_diagram(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" -CLEANUP_FILES = ['performance_diagram_actual.png', 'plot_20200317_151252.points1'] + remove_files(os.environ['TEST_OUTPUT'], expected_files) + performance_diagram.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -@pytest.fixture -def setup(setup_env, remove_files): - setup_env(cwd) - # Cleanup the plotfile and point1 output file from any previous run - remove_files(cwd, CLEANUP_FILES) - custom_config_filename = f"{cwd}/custom_performance_diagram.yaml" - # print("\n current directory: ", os.getcwd()) - # print("\ncustom config file: ", custom_config_filename, '\n') - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - # Retrieve the contents of the custom config file to over-ride - # or augment settings defined by the default config file. - pd.main(custom_config_filename) - - -@pytest.mark.parametrize("test_input,expected_bool",(["performance_diagram_actual.png", True], ["plot_20200317_151252.points1", False])) -def test_plot_exists(setup, test_input, expected_bool, remove_files): - ''' - Checking that only the plot file is getting created and the - .point1 data file is not (dump_points_1 is 'False' in the test config file) - ''' - - assert os.path.isfile(f"{cwd}/{test_input}") == expected_bool - remove_files(cwd, CLEANUP_FILES) - -@pytest.mark.parametrize("test_input,expected_bool",(["./performance_diagram_actual_points1.png", True], ["./intermed_files/plot_20200317_151252.points1", True])) -def test_files_exist(setup_env, test_input, expected_bool, remove_files): - ''' - Checking that only the plot file is getting created and the - .point1 data file is not (dump_points_1 is 'False' in the test config file) - ''' - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_performance_diagram_points1.yaml" - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - # Retrieve the contents of the custom config file to over-ride - # or augment settings defined by the default config file. - pd.main(custom_config_filename) - - assert os.path.isfile(f"{cwd}/{test_input}") == expected_bool - remove_files(cwd, ['performance_diagram_actual_points1.png', 'intermed_files/plot_20200317_151252.points1']) - try: - os.rmdir(os.path.join(cwd, 'intermed_files')) - except OSError: - pass - - -@pytest.mark.parametrize("test_input,expected_bool", (["performance_diagram_defaultpoints1.png", True], ["plot_20200317_151252.points1", True])) -def test_files_exist(setup_env, test_input, expected_bool, remove_files): - ''' - Checking that only the plot file is getting created and the - .point1 data file is not (dump_points_1 is 'False' in the test config file) - ''' - setup_env(cwd) - custom_config_filename = f"{cwd}/custom_performance_diagram_defaultpoints1.yaml" - try: - os.mkdir(os.path.join(cwd, 'intermed_files')) - except FileExistsError: - pass - - # Invoke the command to generate a Performance Diagram based on - # the test_custom_performance_diagram.yaml custom config file. - # Retrieve the contents of the custom config file to over-ride - # or augment settings defined by the default config file. - pd.main(custom_config_filename) - - assert os.path.isfile(f"{cwd}/{test_input}") == expected_bool - remove_files(cwd, ['performance_diagram_defaultpoints1.png', 'plot_20200317_151252.points1']) - try: - os.rmdir(os.path.join(cwd, 'intermed_files')) - except OSError: - pass - - -@pytest.mark.skip() -def test_images_match(setup, remove_files): - ''' - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - ''' - custom_config_filename = f"{cwd}/custom_performance_diagram.yaml" - pd.main(custom_config_filename) - - plot_file = 'performance_diagram_actual.png' - actual_file = os.path.join(cwd, plot_file) - comparison = CompareImages(f'{cwd}/performance_diagram_expected.png', actual_file) - assert comparison.mssim == 1 - remove_files(cwd, CLEANUP_FILES) + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") From 908ddd6bd70336e2478317c25c4c85622d3c671b Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:39:59 -0700 Subject: [PATCH 26/31] clean up tests to be consistent with other tests --- test/bar/bar_with_nones.yaml | 12 +- test/bar/test_bar.py | 59 ++---- test/box/test_box.py | 26 +-- test/contour/test_contour.py | 27 +-- test/eclv/test_eclv.py | 16 +- test/ens_ss/test_ens_ss.py | 32 +--- .../test_equivalence_testing_bounds.py | 26 +-- test/line/custom_line2.yaml | 8 +- test/line/env_fcst_var.yaml | 2 +- test/line/fbias_fixed_vars_vals.yaml | 2 +- test/line/mv_custom_vert_line.yaml | 3 +- test/line/test_line_groups_plot.py | 38 ---- test/line/test_line_plot.py | 173 ++++++++---------- test/mpr_plot/test_mpr_plot.py | 5 +- test/revision_box/test_revision_box.py | 5 +- test/revision_series/test_revision_series.py | 5 +- 16 files changed, 157 insertions(+), 282 deletions(-) delete mode 100644 test/line/test_line_groups_plot.py diff --git a/test/bar/bar_with_nones.yaml b/test/bar/bar_with_nones.yaml index f5714e53..1ddb42d2 100644 --- a/test/bar/bar_with_nones.yaml +++ b/test/bar/bar_with_nones.yaml @@ -1,3 +1,6 @@ +stat_input: !ENV '${TEST_DIR}/bar_with_nones.data' +plot_filename: !ENV '${TEST_OUTPUT}/bar_with_nones.png' + alpha: 0.05 caption_align: 0.0 caption_col: '#333333' @@ -94,12 +97,6 @@ plot_type: png16m plot_units: in plot_width: 11.0 -# Optional, uncomment and set to directory to store the .points1 file -# that is used by METviewer (created when dump_points_1 is set to True) -# if dump_points_1 is True and this is uncommented, the points1 file -# will be saved in the default location (i.e. where the input data file is stored). -points_path: - random_seed: null series_order: @@ -163,8 +160,5 @@ ytlab_orient: 1 ytlab_perp: 0.5 ytlab_size: 1 -stat_input: !ENV '${TEST_DIR}/bar_with_nones.data' -plot_filename: !ENV '${TEST_OUTPUT}/bar_with_nones.png' - show_legend: -True \ No newline at end of file diff --git a/test/bar/test_bar.py b/test/bar/test_bar.py index 9f85cb1f..03cc1db9 100644 --- a/test/bar/test_bar.py +++ b/test/bar/test_bar.py @@ -4,59 +4,34 @@ from metplotpy.plots.bar import bar -cwd = os.path.dirname(__file__) -@pytest.fixture -def setup(remove_files, module_setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files'), ['bar.points1']) - remove_files(os.environ['TEST_OUTPUT'], ['bar.png']) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_bar.yaml", ["bar.png", "intermed_files/bar.points1"]), + ("bar_with_nones.yaml", ["bar_with_nones.png"]), + ("threshold_bar.yaml", ["threshold_bar.png"]), +]) +def test_bar(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" - custom_config_filename = f"{cwd}/custom_bar.yaml" - bar.main(custom_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + bar.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -@pytest.fixture -def setup_nones(remove_files, module_setup_env): - # Cleanup the plotfile from any previous run - remove_files(os.environ['TEST_OUTPUT'], ['bar_with_nones.png']) - custom_config_filename = f"{cwd}/bar_with_nones.yaml" + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") - bar.main(custom_config_filename) + if input_yaml == "custom_bar.yaml": + check_custom_bar_nans() -def test_custom_bar(setup, remove_files): - """Checking that the plot and data files are getting created and - that there are no NaNs in the data file. Check for NaNs in a file that - is known to have NaNs to ensure check works as expected.""" - check_files = ("bar.png", "intermed_files/bar.points1") - for check_file in check_files: - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{check_file}") - +def check_custom_bar_nans(): + """Checking that there are no NaNs in the data file. + Check for NaNs in a file that is known to have NaNs to ensure check works as expected.""" with open(f"{os.environ['TEST_OUTPUT']}/intermed_files/bar.points1", "r") as f: data = f.read() assert "NaN" not in data # Verify that the nan.points1 file does indeed trigger a "nans_found" - with open(f"{cwd}/nan.points1", "r") as f: + with open(f"{os.environ['TEST_DIR']}/nan.points1", "r") as f: data = f.read() assert "NaN" in data - - -def test_bar_with_nones(setup_nones): - """ - Compare an expected plot with the - newly created plot to verify that the plot hasn't - changed in appearance. - """ - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/bar_with_nones.png") - - -def test_threshold_plotting(module_setup_env, remove_files): - """Verify that the bar plot using data with thresholds is correct.""" - # Cleanup the plotfile and point1 output file from any previous run - remove_files(os.environ['TEST_OUTPUT'], ['threshold_bar.png']) - - custom_config_filename = f"{cwd}/threshold_bar.yaml" - bar.main(custom_config_filename) - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/threshold_bar.png") diff --git a/test/box/test_box.py b/test/box/test_box.py index c749a253..83bb9e2a 100644 --- a/test/box/test_box.py +++ b/test/box/test_box.py @@ -1,25 +1,17 @@ import pytest import os from metplotpy.plots.box import box -#from metcalcpy.compare_images import CompareImages -cwd = os.path.dirname(__file__) -CLEANUP_FILES = ['box.png', 'box.points1'] -@pytest.fixture -def setup(remove_files, module_setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files'), ['box.points1']) - remove_files(os.environ['TEST_OUTPUT'], ['box.png']) +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_box.yaml", ["box.png", "intermed_files/box.points1"]), +]) +def test_box(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" - custom_config_filename = f"{cwd}/custom_box.yaml" - box.main(custom_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + box.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -def test_custom_box(setup, remove_files): - """ - Checking that the plot and data files are getting created - """ - check_files = ("box.png", "intermed_files/box.points1") - for check_file in check_files: - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{check_file}") + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/contour/test_contour.py b/test/contour/test_contour.py index 80c83ce3..c1843d54 100644 --- a/test/contour/test_contour.py +++ b/test/contour/test_contour.py @@ -2,25 +2,16 @@ import os from metplotpy.plots.contour import contour -cwd = os.path.dirname(__file__) -@pytest.fixture -def setup(module_setup_env): - # Cleanup the plotfile output file from any previous run - cleanup() +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_contour.yaml", ["contour.png"]), +]) +def test_contour(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" - contour.main(f"{cwd}/custom_contour.yaml") + remove_files(os.environ['TEST_OUTPUT'], expected_files) + contour.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -def cleanup(): - # remove the previously created files - try: - plot_file = 'contour.png' - os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) - except OSError: - pass - - -def test_files_exist(setup): - """Checking that the plot files are getting created""" - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'contour.png')) + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/eclv/test_eclv.py b/test/eclv/test_eclv.py index aca4467b..3812f24c 100644 --- a/test/eclv/test_eclv.py +++ b/test/eclv/test_eclv.py @@ -2,16 +2,6 @@ import os from metplotpy.plots.eclv import eclv -cwd = os.path.dirname(__file__) - - -def cleanup(plot_file): - # remove the previously created files - try: - os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) - except OSError: - pass - @pytest.mark.parametrize( "yaml_file, expected_output", [ @@ -20,10 +10,10 @@ def cleanup(plot_file): ("custom_eclv_ctc.yaml", "eclv_ctc.png"), ] ) -def test_files_exist(module_setup_env, yaml_file, expected_output): +def test_files_exist(module_setup_env, remove_files, yaml_file, expected_output): """ Checking that the plot files are getting created """ - cleanup(expected_output) - eclv.main(f"{cwd}/{yaml_file}") + remove_files(os.environ['TEST_OUTPUT'], expected_output) + eclv.main(f"{os.environ['TEST_DIR']}/{yaml_file}") assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_output)) diff --git a/test/ens_ss/test_ens_ss.py b/test/ens_ss/test_ens_ss.py index d7205d8b..744e2dcb 100644 --- a/test/ens_ss/test_ens_ss.py +++ b/test/ens_ss/test_ens_ss.py @@ -3,30 +3,16 @@ from metplotpy.plots.ens_ss import ens_ss -cwd = os.path.dirname(__file__) -@pytest.fixture -def setup(module_setup_env): - # Cleanup the plotfile and point1 output file from any previous run - cleanup() +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_ens_ss.yaml", ["ens_ss.png", "intermed_files/ens_ss.points1"]), +]) +def test_ens_ss(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" - custom_config_filename = f"{cwd}/custom_ens_ss.yaml" - ens_ss.main(custom_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + ens_ss.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -def cleanup(): - # remove the .png and .points files - # from any previous runs - try: - plot_file = 'ens_ss.png' - points_file_1 = 'intermed_files/ens_ss.points1' - os.remove(os.path.join(os.environ['TEST_OUTPUT'], plot_file)) - os.remove(os.path.join(os.environ['TEST_OUTPUT'], points_file_1)) - except OSError: - pass - - -def test_custom_ens_ss(setup): - """Checking that the plot and data files are getting created""" - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'ens_ss.png')) - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], 'intermed_files', 'ens_ss.points1')) + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py b/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py index a3f27872..2c563b31 100644 --- a/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py +++ b/test/equivalence_testing_bounds/test_equivalence_testing_bounds.py @@ -1,20 +1,20 @@ import pytest import os -from metplotpy.plots.equivalence_testing_bounds import equivalence_testing_bounds as etb -cwd = os.path.dirname(__file__) +from metplotpy.plots.equivalence_testing_bounds import equivalence_testing_bounds as etb -@pytest.fixture -def setup(remove_files, module_setup_env): - # Cleanup the plotfile and point1 output file from any previous run - remove_files(os.environ['TEST_OUTPUT'], 'equivalence_testing_bounds.png') - remove_files(os.environ['TEST_OUTPUT'], 'intermed_files/equivalence_testing_bounds.points1') +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_equivalence_testing_bounds.yaml", [ + "equivalence_testing_bounds.png", + "intermed_files/equivalence_testing_bounds.points1", + ]), +]) +def test_equivalence_testing_bounds(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" - custom_config_filename = f"{cwd}/custom_equivalence_testing_bounds.yaml" - etb.main(custom_config_filename) + remove_files(os.environ['TEST_OUTPUT'], expected_files) + etb.main(f"{os.environ['TEST_DIR']}/{input_yaml}") -def test_files_exist(setup): - """Checking that the plot and data files are getting created""" - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/equivalence_testing_bounds.png") - assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/intermed_files/equivalence_testing_bounds.points1") + for expected_file in expected_files: + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/line/custom_line2.yaml b/test/line/custom_line2.yaml index 52c1350b..7f5a0cd9 100644 --- a/test/line/custom_line2.yaml +++ b/test/line/custom_line2.yaml @@ -1,3 +1,8 @@ +plot_filename: !ENV '${TEST_OUTPUT}/line.png' +points_path: !ENV '${TEST_OUTPUT}/custom_line2' + +stat_input: !ENV '${TEST_DIR}/line.data' + alpha: 0.05 box_avg: 'False' box_boxwex: 0.2 @@ -95,14 +100,12 @@ plot_disp: - 'True' - 'True' - 'True' -plot_filename: !ENV '${TEST_OUTPUT}/line.png' plot_height: 8.5 plot_res: 72 plot_stat: median plot_type: png16m plot_units: in plot_width: 11.0 -points_path: !ENV '${TEST_OUTPUT}/intermed_files' random_seed: null series_line_style: - '-' @@ -149,7 +152,6 @@ show_signif: - 'False' - 'False' - 'False' -stat_input: !ENV '${TEST_DIR}/line.data' sync_yaxes: 'False' title: test title title_align: 0.5 diff --git a/test/line/env_fcst_var.yaml b/test/line/env_fcst_var.yaml index 2e926464..5b9454b8 100644 --- a/test/line/env_fcst_var.yaml +++ b/test/line/env_fcst_var.yaml @@ -1,5 +1,5 @@ plot_filename: !ENV '${TEST_OUTPUT}/env_fcst_var_stat_line.png' -points_path: !ENV '${TEST_OUTPUT}/intermed_files/env_fcst_var_stat' +points_path: !ENV '${TEST_OUTPUT}/env_fcst_var' dump_points_1: 'True' dump_points_2: 'True' diff --git a/test/line/fbias_fixed_vars_vals.yaml b/test/line/fbias_fixed_vars_vals.yaml index d0a468f2..6c6f90de 100644 --- a/test/line/fbias_fixed_vars_vals.yaml +++ b/test/line/fbias_fixed_vars_vals.yaml @@ -1,5 +1,5 @@ plot_filename: !ENV '${TEST_OUTPUT}/fbias_fixed_vars.png' -points_path: !ENV '${TEST_OUTPUT}' +points_path: !ENV '${TEST_OUTPUT}/fbias_points' stat_input: !ENV '${TEST_DIR}/fbias_data.txt' alpha: 0.05 diff --git a/test/line/mv_custom_vert_line.yaml b/test/line/mv_custom_vert_line.yaml index 49a4183a..3ea45e32 100644 --- a/test/line/mv_custom_vert_line.yaml +++ b/test/line/mv_custom_vert_line.yaml @@ -1,5 +1,6 @@ -points_path: !ENV '${TEST_OUTPUT}/intermed_files' +points_path: !ENV '${TEST_OUTPUT}/mv_custom_vert_line' plot_filename: !ENV '${TEST_OUTPUT}/vert_line_plot.png' + stat_input: !ENV '${TEST_DIR}/vert_line_plot_data.txt' alpha: 0.05 diff --git a/test/line/test_line_groups_plot.py b/test/line/test_line_groups_plot.py deleted file mode 100644 index ec55d375..00000000 --- a/test/line/test_line_groups_plot.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -import os -from metplotpy.plots.line import line as l - -cwd = os.path.dirname(__file__) - - -def test_custom_line_groups(module_setup_env, remove_files): - """Checking that the plot and data files are getting created""" - expected_files = ( - "line_groups.png", - "line_groups.points1", - "line_groups.points2", - "line_groups.html", - ) - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - l.main(f"{cwd}/custom_line_groups.yaml") - - for expected_file in expected_files: - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) - - -def test_custom_line_groups2(module_setup_env, remove_files): - """Checking that the plot and data files are getting created""" - expected_files = ( - "line_groups2.png", - "intermed_files/line_groups.points1", - "intermed_files/line_groups.points2", - ) - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - l.main(f"{cwd}/custom_line_groups2.yaml") - - for expected_file in expected_files: - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) diff --git a/test/line/test_line_plot.py b/test/line/test_line_plot.py index 165a62e4..648b6dae 100644 --- a/test/line/test_line_plot.py +++ b/test/line/test_line_plot.py @@ -1,98 +1,107 @@ -import pandas as pd import pytest -import os -from metplotpy.plots.line import line as l -cwd = os.path.dirname(__file__) +import pandas as pd +import os +from metplotpy.plots.line import line + + +@pytest.mark.parametrize("input_yaml,expected_files", [ + ("custom_line.yaml", [ + "line.png", + ]), + ("custom_line2.yaml", [ + "line.png", + "custom_line2/line.points1", + "custom_line2/line.points2", + ]), + ("custom_line_from_zero.yaml", [ + "line_from_zero.png", + ]), + ("env_fcst_var.yaml", [ + "env_fcst_var_stat_line.png", + "env_fcst_var/line.points1", + "env_fcst_var/line.points2", + ]), + ("mv_custom_vert_line.yaml", [ + "vert_line_plot.png", + "mv_custom_vert_line/vert_line_plot.points1", + "mv_custom_vert_line/vert_line_plot.points2", + ]), + ("fbias_fixed_vars_vals.yaml", [ + "fbias_fixed_vars.png", + "fbias_points/fbias.points1", + ]), + # formerly in test_line_groups_plots.py + ("custom_line_groups.yaml", [ + "line_groups.png", + "line_groups.points1", + "line_groups.points2", + "line_groups.html", + ]), + ("custom_line_groups2.yaml", [ + "line_groups2.png", + "intermed_files/line_groups.points1", + "intermed_files/line_groups.points2", + ]), +]) +def test_line_plot(module_setup_env, remove_files, input_yaml, expected_files): + """Checking that the plot file is getting created""" -def test_custom_line_without_points(module_setup_env, remove_files): - """Checking that the plot file is getting created but the .points1 and .points2 files are NOT""" - output_dir = os.environ['TEST_OUTPUT'] - expected_files = ['line.png'] - not_expected_files = ['line.points1', 'line.points2'] + remove_files(os.environ['TEST_OUTPUT'], expected_files) - remove_files(os.environ['TEST_OUTPUT'], expected_files + not_expected_files) + # used by env_fcst_var.yaml + os.environ['FCST_VAR_VAL1'] = "RH" + os.environ['FCST_VAR_STAT1'] = 'MAE' + os.environ['FCST_VAR_VAL2'] = "TMP" + os.environ['FCST_VAR_STAT2'] = 'ME' - l.main(f"{cwd}/custom_line.yaml") + line.main(f"{os.environ['TEST_DIR']}/{input_yaml}") for expected_file in expected_files: - assert os.path.isfile(os.path.join(output_dir, expected_file)) + assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") - for not_expected_file in not_expected_files: - assert not os.path.isfile(os.path.join(output_dir, not_expected_file)) + if input_yaml == "custom_line2.yaml": + check_custom_line2_nans() + if input_yaml == "mv_custom_vert_line.yaml": + check_vertical_plot() -def test_custom_line2_with_points(module_setup_env, remove_files): - """Checking that the plot and point data files are getting created. - Checking that no NaNs are found in the pointsN files""" - output_dir = os.environ['TEST_OUTPUT'] - expected_files = ( - 'line.png', - 'intermed_files/line.points1', - 'intermed_files/line.points2' - ) + if input_yaml == "env_fcst_var.yaml": + check_env_fcst_var() - remove_files(os.environ['TEST_OUTPUT'], expected_files) + if input_yaml == "fbias_fixed_vars_vals.yaml": + check_fbias_fixed_vars_vals() - l.main(f"{cwd}/custom_line2.yaml") - - for expected_file in expected_files: - assert os.path.isfile(os.path.join(output_dir, expected_file)) +def check_custom_line2_nans(): + """Checking that no NaNs are found in the pointsN files""" # Check for NaN's in the intermediate files,_line.points1 and line.points2 # Fail if there are any NaN's-this indicates something went wrong with the # line_series.py module's _create_series_points() method. - with open(f"{output_dir}/intermed_files/line.points1", "r") as f: + with open(f"{os.environ['TEST_OUTPUT']}/custom_line2/line.points1", "r") as f: data = f.read() assert "NaN" not in data - with open(f"{output_dir}/intermed_files/line.points2", "r") as f: + with open(f"{os.environ['TEST_OUTPUT']}/custom_line2/line.points2", "r") as f: data = f.read() assert "NaN" not in data # Verify that the nan.points1 file does indeed trigger a "nans_found" - with open(f"{cwd}/nan.points1", "r") as f: + with open(f"{os.environ['TEST_DIR']}/nan.points1", "r") as f: data = f.read() assert "NaN" in data -def test_custom_line_from_zero(module_setup_env, remove_files): - """Compare an expected plot with the start_at_zero option, with the - newly created plot to verify that the plot hasn't changed in appearance.""" - expected_files = [ - 'line_from_zero.png' - ] - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - l.main(f"{cwd}/custom_line_from_zero.yaml") - - assert(os.path.isfile(f"{os.environ['TEST_OUTPUT']}/line_from_zero.png")) - - -def test_vertical_plot(module_setup_env, remove_files): +def check_vertical_plot(): """Test that the y1 values from the Python version of the vertical plot match the y1 values from the METviewer Rplot version of the vertical plot. Avoid relying on image comparison tests.""" - expected_files = [ - 'vert_line_plot.png', - 'intermed_files/vert_line_plot.points1' - ] - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - l.main(f"{cwd}/mv_custom_vert_line.yaml") - - for expected_file in expected_files: - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) - - output_dir = os.environ['TEST_OUTPUT'] # Retrieve the .points1 files generated by METviewer and METplotpy respectively - mv_df = pd.read_csv(f'{cwd}/intermed_files/vert_plot_y1_from_metviewer.points1', + mv_df = pd.read_csv(f"{os.environ['TEST_DIR']}/intermed_files/vert_plot_y1_from_metviewer.points1", sep=" ", header=None) - mpp_df = pd.read_csv(f'{output_dir}/intermed_files/vert_line_plot.points1', sep=" ", + mpp_df = pd.read_csv(f"{os.environ['TEST_OUTPUT']}/mv_custom_vert_line/vert_line_plot.points1", sep=" ", header=None) # ----------------------- @@ -119,24 +128,13 @@ def test_vertical_plot(module_setup_env, remove_files): assert sum_diff < 0.00001 -def test_fbias_fixed_vars_vals(module_setup_env, remove_files): +def check_fbias_fixed_vars_vals(): """Verify that the fixed_vars_vals_input setting reproduces the same data points that METviewer produces.""" - expected_files = ( - 'fbias_fixed_vars.png', - 'fbias.points1', - ) - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - l.main(f"{cwd}/fbias_fixed_vars_vals.yaml") - - for expected_file in expected_files: - assert os.path.isfile(os.path.join(os.environ['TEST_OUTPUT'], expected_file)) # Retrieve the .points1 files generated by METviewer and METplotpy respectively - mv_df = pd.read_csv(f'{cwd}/intermed_files/mv_fixed_var_vals.points1', + mv_df = pd.read_csv(f"{os.environ['TEST_DIR']}/intermed_files/mv_fixed_var_vals.points1", sep="\t", header=None) - mpp_df = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/fbias.points1', sep="\t", header=None) + mpp_df = pd.read_csv(f"{os.environ['TEST_OUTPUT']}/fbias_points/fbias.points1", sep="\t", header=None) # Verify that the values in the generated points1 file are identical # to those in the METviewer points1 file. @@ -155,28 +153,13 @@ def test_fbias_fixed_vars_vals(module_setup_env, remove_files): assert mv_df.iloc[i][0] == mpp_df.iloc[i][0] -def test_envs_fcst_var_stat(module_setup_env, remove_files): +def check_env_fcst_var(): """Verify that the environment vars used for fcst_var_val1/2 and stat1/2 in a config file creates the same data for plotting as a config file with the fcst_var_val1 and stat hard-coded in a config file.""" - expected_files = ( - 'env_fcst_var_stat_line.png', - 'intermed_files/env_fcst_var_stat/line.points1', - 'intermed_files/env_fcst_var_stat/line.points2', - ) - - remove_files(os.environ['TEST_OUTPUT'], expected_files) - - os.environ['FCST_VAR_VAL1'] = "RH" - os.environ['FCST_VAR_STAT1'] = 'MAE' - os.environ['FCST_VAR_VAL2'] = "TMP" - os.environ['FCST_VAR_STAT2'] = 'ME' - - l.main(f"{cwd}/env_fcst_var.yaml") - - expected_points1 = f"{cwd}/intermed_files/expected_var_stat_line.points1" - expected_points2 = f"{cwd}/intermed_files/expected_var_stat_line.points2" + expected_points1 = f"{os.environ['TEST_DIR']}/intermed_files/expected_var_stat_line.points1" + expected_points2 = f"{os.environ['TEST_DIR']}/intermed_files/expected_var_stat_line.points2" expected_df1 = pd.read_csv(f'{expected_points1}', sep="\t", header=None) expected_df2 = pd.read_csv(f'{expected_points2}', sep="\t", header=None) num_expected1_rows = expected_df1.shape[0] @@ -185,9 +168,9 @@ def test_envs_fcst_var_stat(module_setup_env, remove_files): num_expected2_cols = expected_df2.shape[1] # Retrieve the .points1 files generated by METplotpy respectively - mpp_df1 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/intermed_files/env_fcst_var_stat/line.points1', + mpp_df1 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/env_fcst_var/line.points1', sep="\t", header=None) - mpp_df2 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/intermed_files/env_fcst_var_stat/line.points2', sep="\t", header=None) + mpp_df2 = pd.read_csv(f'{os.environ['TEST_OUTPUT']}/env_fcst_var/line.points2', sep="\t", header=None) # Verify that the values in the generated points1/2 files are identical # to those in the rh_mae_tmp_me_line.points1/2 files. diff --git a/test/mpr_plot/test_mpr_plot.py b/test/mpr_plot/test_mpr_plot.py index 5076af05..e5972c36 100644 --- a/test/mpr_plot/test_mpr_plot.py +++ b/test/mpr_plot/test_mpr_plot.py @@ -1,8 +1,9 @@ import pytest + import os + from metplotpy.plots.mpr_plot import mpr_plot -cwd = os.path.dirname(__file__) def test_files_exist(module_setup_env, remove_files): """Checking that the plot and data files are getting created""" @@ -10,6 +11,6 @@ def test_files_exist(module_setup_env, remove_files): remove_files(os.environ['TEST_OUTPUT'], expected_file) - mpr_plot.main(f"{cwd}/mpr_plot_custom.yaml") + mpr_plot.main(f"{os.environ['TEST_DIR']}/mpr_plot_custom.yaml") assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/revision_box/test_revision_box.py b/test/revision_box/test_revision_box.py index 95f42919..f6e693eb 100644 --- a/test/revision_box/test_revision_box.py +++ b/test/revision_box/test_revision_box.py @@ -1,8 +1,7 @@ import pytest import os -from metplotpy.plots.revision_box import revision_box -cwd = os.path.dirname(__file__) +from metplotpy.plots.revision_box import revision_box def test_custom_revision_box(module_setup_env, remove_files): """Checking that the plot and data files are getting created""" @@ -13,7 +12,7 @@ def test_custom_revision_box(module_setup_env, remove_files): remove_files(os.environ['TEST_OUTPUT'], expected_files) - revision_box.main(f"{cwd}/custom_revision_box.yaml") + revision_box.main(f"{os.environ['TEST_DIR']}/custom_revision_box.yaml") for expected_file in expected_files: assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") diff --git a/test/revision_series/test_revision_series.py b/test/revision_series/test_revision_series.py index 94cf0865..d8bc43b0 100644 --- a/test/revision_series/test_revision_series.py +++ b/test/revision_series/test_revision_series.py @@ -1,8 +1,7 @@ import pytest import os -from metplotpy.plots.revision_series import revision_series -cwd = os.path.dirname(__file__) +from metplotpy.plots.revision_series import revision_series @pytest.mark.parametrize("input_yaml, expected_files", [ ("custom_revision_series.yaml", ["revision_series.png", "intermed_files/revision_series.points1"]), @@ -12,7 +11,7 @@ def test_files_exist(module_setup_env, remove_files, input_yaml, expected_files) remove_files(os.environ['TEST_OUTPUT'], expected_files) - revision_series.main(f"{cwd}/{input_yaml}") + revision_series.main(f"{os.environ['TEST_DIR']}/{input_yaml}") for expected_file in expected_files: assert os.path.isfile(f"{os.environ['TEST_OUTPUT']}/{expected_file}") From 05b78c0bdff54dc1e8e3c1439cffef4f03f359fe Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:38:13 -0700 Subject: [PATCH 27/31] update tests to run pytest from top level to run all tests and set PYTHONPATH to find METcalcpy --- .github/workflows/unit_tests.yaml | 40 ++----------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index 702a7ec8..61210f21 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -70,41 +70,5 @@ jobs: - name: Test with pytest run: | - cd test - cd scatter - pytest test_scatter.py - cd ../bar - pytest test_bar.py - cd ../box - pytest test_box.py - cd ../contour - pytest test_contour.py - cd ../eclv - pytest test_eclv.py - cd ../ens_ss - pytest test_ens_ss.py - cd ../equivalence_testing_bounds - pytest test_equivalence_testing_bounds.py - cd ../line - pytest test_line_groups_plot.py - pytest test_line_plot.py - cd ../mpr_plot - pytest test_mpr_plot.py - cd ../performance_diagram - pytest test_performance_diagram.py - cd ../reliability_diagram - pytest test_reliability_diagram.py - cd ../roc_diagram - pytest test_roc_diagram.py - cd ../taylor_diagram - pytest test_taylor_diagram.py - cd ../wind_rose - pytest test_wind_rose.py - cd ../histogram - pytest test_prob_hist.py - pytest test_rank_hist.py - pytest test_rel_hist.py - cd ../tcmpr_plots - pytest --capture=fd test_tcmpr_plots.py - - + export PYTHONPATH=$PYTHONPATH:$RUNNER_WORKSPACE/METcalcpy + pytest From 20c62e1b6c1fdcc954c974171870f22e23c73c1d Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:39:00 -0700 Subject: [PATCH 28/31] don't need to set PYTHONPATH because METcalcpy is installed via pip --- .github/workflows/unit_tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index 61210f21..5d0c04f5 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -70,5 +70,4 @@ jobs: - name: Test with pytest run: | - export PYTHONPATH=$PYTHONPATH:$RUNNER_WORKSPACE/METcalcpy pytest From 52bc09d9368fad88e3e5974a4df52f608588a9ce Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:35:36 -0700 Subject: [PATCH 29/31] use raw string to get rid of SyntaxWarning for invalid escape sequence \s --- metplotpy/plots/skew_t/skew_t.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metplotpy/plots/skew_t/skew_t.py b/metplotpy/plots/skew_t/skew_t.py index 4bce8b15..1c171f57 100644 --- a/metplotpy/plots/skew_t/skew_t.py +++ b/metplotpy/plots/skew_t/skew_t.py @@ -66,7 +66,7 @@ def extract_sounding_data(input_file, output_directory): txt_file.write("".join(line) + "\n") # Read in the current sounding data file, replacing any 9999 values with NaN. - df_raw: pandas.DataFrame = pd.read_csv(sounding_data_file, sep='\s+', skiprows=1, engine='python') + df_raw: pandas.DataFrame = pd.read_csv(sounding_data_file, sep=r'\s+', skiprows=1, engine='python') df_raw.replace('9999', 'NA', inplace=True) # Rename some columns so they are more descriptive From e9591bfdb547bd924a15a368e2786c1efb10cc42 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:15:47 -0700 Subject: [PATCH 30/31] remove setup_env fixture because it has fully been replaced by module_setup_env module-scope fixture --- test/conftest.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 2722d95a..97f294c7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -83,21 +83,6 @@ def compare_json(fig, expected_json_file): return compare_json -@pytest.fixture -def setup_env(): - def set_environ(test_dir): - print("Setting up environment") - os.environ['TEST_DIR'] = test_dir - # write test output under METPLOTPY_TEST_OUTPUT if set, - # otherwise write to test/test_output/ - output_dir = os.environ.get('METPLOTPY_TEST_OUTPUT', - os.path.join(test_dir, os.pardir)) - # write to a subdirectory named after the plot type - os.environ['TEST_OUTPUT'] = os.path.join(output_dir, 'test_output', os.path.basename(test_dir)) - - return set_environ - - @pytest.fixture(scope="module") def module_setup_env(request): """Module-scoped fixture that sets up environment variables once per test module. From 34751efd8cd1f26016d31641a3bbb8d77f146d4d Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:43:16 -0700 Subject: [PATCH 31/31] restore file that is referenced in User's Guide --- .../custom_reliability_use_defaults.yaml | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 test/reliability_diagram/custom_reliability_use_defaults.yaml diff --git a/test/reliability_diagram/custom_reliability_use_defaults.yaml b/test/reliability_diagram/custom_reliability_use_defaults.yaml new file mode 100644 index 00000000..b2a2d711 --- /dev/null +++ b/test/reliability_diagram/custom_reliability_use_defaults.yaml @@ -0,0 +1,187 @@ +add_noskill_line: 'True' +add_reference_line: 'True' +add_skill_line: 'True' +alpha: 0.05 +box_avg: 'False' +box_boxwex: 0.2 +box_notch: 'False' +box_outline: 'True' +box_pts: 'False' +caption_align: 0.0 +caption_col: '#ff1493' +caption_offset: 3.0 +caption_size: 1.0 +caption_weight: 1 +cex: 1 +colors: +- '#ff0000' +- '#00ff7f' +- '#8000ff' +con_series: +- 1 +- 1 +- 1 +create_html: 'False' +derived_series_1: [] +derived_series_2: [] +dump_points_1: 'True' +dump_points_2: 'False' +event_equal: 'True' +fcst_var_val_1: + APCP_03_ENS_FREQ_ge12.7: + - PSTD_CALIBRATION + - PSTD_BASER + - PSTD_NI +fcst_var_val_2: null +fixed_vars_vals_input: {} +grid_col: '#cccccc' +grid_lty: 3 +grid_lwd: 1 +grid_on: 'True' +grid_x: listX +indy_label: [] +indy_stagger_1: 'True' +indy_stagger_2: 'False' +indy_vals: +- '0' +- '0.1' +- '0.2' +- '0.3' +- '0.4' +- '0.5' +- '0.6' +- '0.7' +- '0.8' +- '0.9' +indy_var: thresh_i +inset_hist: 'True' +legend_box: o +legend_inset: + x: 0.0 + y: -0.25 +legend_ncol: 3 +legend_size: 0.8 +line_type: pct +list_stat_1: +- PSTD_CALIBRATION +- PSTD_BASER +- PSTD_NI +list_stat_2: [] +list_static_val: + fcst_var: APCP_03_ENS_FREQ_ge12.7 +mar: +- 8 +- 4 +- 5 +- 4 +method: perc +mgp: +- 1 +- 1 +- 0 +num_iterations: 1000 +num_threads: -1 +plot_caption: capcap uuu +plot_ci: +- boot +- boot +- none +plot_disp: +- 'True' +- 'True' +- 'True' +plot_height: 8.5 +plot_res: 72 +plot_stat: median +plot_type: png16m +plot_units: in +plot_width: 11.0 +random_seed: null +rely_event_hist: 'True' +series_line_style: +- '-' +- '-' +- '-' +series_line_width: +- 1 +- 1 +- 1 +series_order: +- 1 +- 2 +- 3 +series_symbols: +- . +- . +- . +series_type: +- b +- b +- b +series_val_1: + model: + - rap0_3_spptens + - rap0_7_spptens +series_val_2: {} +show_nstats: 'False' +show_signif: +- 'False' +- 'False' +- 'False' +summary_curves: +- mean +sync_yaxes: 'False' +title: The
test +title_align: 0.5 +title_offset: -2 +title_size: 1.6 +title_weight: 4.0 +user_legend: [] +variance_inflation_factor: 'False' +vert_plot: 'False' +x2lab_align: 0.5 +x2lab_offset: -0.5 +x2lab_size: 0.8 +x2lab_weight: 1 +x2tlab_horiz: 0.5 +x2tlab_orient: 1 +x2tlab_perp: 1 +x2tlab_size: 0.8 +xaxis: some x +xaxis_reverse: 'False' +xlab_align: 0.5 +xlab_offset: 2 +xlab_size: 1 +xlab_weight: 2 +xlim: [] +xtlab_decim: 0 +xtlab_horiz: 0.5 +xtlab_orient: 1 +xtlab_perp: -0.75 +xtlab_size: 1 +y2lab_align: 0.5 +y2lab_offset: 1 +y2lab_size: 1 +y2lab_weight: 1 +y2lim: [] +y2tlab_horiz: 0.5 +y2tlab_orient: 1 +y2tlab_perp: 1 +y2tlab_size: 1.0 +yaxis_1: some y +yaxis_2: '' +ylab_align: 0.5 +ylab_offset: -2 +ylab_size: 2 +ylab_weight: 3 +ylim: [] +ytlab_horiz: 0.5 +ytlab_orient: 1 +ytlab_perp: 0.5 +ytlab_size: 1 +plot_filename: !ENV '${TEST_DIR}/reliability.png' +stat_input: !ENV '${TEST_DIR}/reliability.data' +show_legend: +- 'True' +- 'True' +- 'True'