Repository: materialsproject/custodian
Pull request #297 (2023-10-19 15:56:20)

Fixed a small issue with the erroneous attribute call on a Structure object in AMIN handler
Recently ran into this issue. `Structure` object doesn't have a `structure` attribute. This is now fixed.

janosh: Thanks @fyalcin! Would you be able to add a test for this?

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5dca34d7..29e0ef2b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,10 +8,11 @@ ci:
 
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.0.292
+    rev: v0.1.3
     hooks:
       - id: ruff
         args: [--fix, --ignore, D]
+      - id: ruff-format
 
   - repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v4.5.0
@@ -21,13 +22,8 @@ repos:
       - id: end-of-file-fixer
       - id: trailing-whitespace
 
-  - repo: https://github.com/psf/black
-    rev: 23.9.1
-    hooks:
-      - id: black
-
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.5.1
+    rev: v1.6.1
     hooks:
       - id: mypy
         additional_dependencies: [types-requests]
diff --git a/custodian/cli/converge_geometry.py b/custodian/cli/converge_geometry.py
old mode 100755
new mode 100644
index 31a74588..dbc6e2c7
--- a/custodian/cli/converge_geometry.py
+++ b/custodian/cli/converge_geometry.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 """This is a script to converge the geometry of a system."""
 
 import logging
diff --git a/custodian/cli/converge_kpoints.py b/custodian/cli/converge_kpoints.py
old mode 100755
new mode 100644
index cc79c645..ad646fbf
--- a/custodian/cli/converge_kpoints.py
+++ b/custodian/cli/converge_kpoints.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 """This is a master vasp running script to converging kpoints for a calculation."""
 
 import logging
diff --git a/custodian/cli/cstdn.py b/custodian/cli/cstdn.py
old mode 100755
new mode 100644
index 47620b3e..9245c3e6
--- a/custodian/cli/cstdn.py
+++ b/custodian/cli/cstdn.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # Copyright (c) Materials Virtual Lab.
 # Distributed under the terms of the BSD License.
 
diff --git a/custodian/cli/run_nwchem.py b/custodian/cli/run_nwchem.py
old mode 100755
new mode 100644
index 15571b4f..f62bbe7a
--- a/custodian/cli/run_nwchem.py
+++ b/custodian/cli/run_nwchem.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 """Basic script to run nwchem job."""
 
 import logging
diff --git a/custodian/cli/run_vasp.py b/custodian/cli/run_vasp.py
old mode 100755
new mode 100644
index 6e130e37..c4778315
--- a/custodian/cli/run_vasp.py
+++ b/custodian/cli/run_vasp.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 """
 This is a master vasp running script to perform various combinations of VASP
 runs.
@@ -9,7 +7,7 @@ import logging
 import sys
 
 from pymatgen.io.vasp.inputs import Incar, Kpoints, VaspInput
-from ruamel import yaml
+from ruamel.yaml import YAML
 
 from custodian.custodian import Custodian
 from custodian.vasp.jobs import VaspJob
@@ -22,7 +20,7 @@ def load_class(mod, name):
     if len(toks) == 2:
         for p in toks[-1].split(","):
             ptoks = p.split("=")
-            params[ptoks[0]] = yaml.safe_load(ptoks[1])
+            params[ptoks[0]] = YAML(typ="rt").load(ptoks[1])
     elif len(toks) > 2:
         print("Bad handler specification")
         sys.exit(-1)
diff --git a/custodian/custodian.py b/custodian/custodian.py
index 83b39bc0..ff7f6f96 100644
--- a/custodian/custodian.py
+++ b/custodian/custodian.py
@@ -508,13 +508,13 @@ class Custodian:
                     if v.check():
                         self.run_log[-1]["validator"] = v
                         s = f"Validation failed: {v.__class__.__name__}"
-                        raise ValidationError(s, True, v)
+                        raise ValidationError(s, raises=True, validator=v)
                 if not zero_return_code:
                     if self.terminate_on_nonzero_returncode:
                         self.run_log[-1]["nonzero_return_code"] = True
                         s = f"Job return code is {p.returncode}. Terminating..."
                         logger.info(s)
-                        raise ReturnCodeError(s, True)
+                        raise ReturnCodeError(s, raises=True)
                     warnings.warn("subprocess returned a non-zero return code. Check outputs carefully...")
                 job.postprocess()
                 return
@@ -524,27 +524,27 @@ class Custodian:
                 if not x["actions"] and x["handler"].raises_runtime_error:
                     self.run_log[-1]["handler"] = x["handler"]
                     s = f"Unrecoverable error for handler: {x['handler']}"
-                    raise NonRecoverableError(s, True, x["handler"])
+                    raise NonRecoverableError(s, raises=True, handler=x["handler"])
             for x in self.run_log[-1]["corrections"]:
                 if not x["actions"]:
                     self.run_log[-1]["handler"] = x["handler"]
                     s = f"Unrecoverable error for handler: {x['handler']}"
-                    raise NonRecoverableError(s, False, x["handler"])
+                    raise NonRecoverableError(s, raises=False, handler=x["handler"])
 
         if self.errors_current_job >= self.max_errors_per_job:
             self.run_log[-1]["max_errors_per_job"] = True
             msg = f"Max errors per job reached: {self.max_errors_per_job}."
             logger.info(msg)
-            raise MaxCorrectionsPerJobError(msg, True, self.max_errors_per_job, job)
+            raise MaxCorrectionsPerJobError(msg, raises=True, max_errors_per_job=self.max_errors_per_job, job=job)
 
         self.run_log[-1]["max_errors"] = True
         msg = f"Max errors reached: {self.max_errors}."
         logger.info(msg)
-        raise MaxCorrectionsError(msg, True, self.max_errors)
+        raise MaxCorrectionsError(msg, raises=True, max_errors=self.max_errors)
 
     def run_interrupted(self):
         """
-        Runs custodian in a interuppted mode, which sets up and
+        Runs custodian in a interrupted mode, which sets up and
         validates jobs but doesn't run the executable.
 
         Returns:
@@ -596,7 +596,7 @@ class Custodian:
                     if not x["actions"] and x["handler"].raises_runtime_error:
                         self.run_log[-1]["handler"] = x["handler"]
                         s = f"Unrecoverable error for handler: {x['handler']}. Raising RuntimeError"
-                        raise NonRecoverableError(s, True, x["handler"])
+                        raise NonRecoverableError(s, raises=True, handler=x["handler"])
                 logger.info("Corrected input based on error handlers")
                 # Return with more jobs to run if recoverable error caught
                 # and corrected for
@@ -609,7 +609,7 @@ class Custodian:
                     self.run_log[-1]["validator"] = v
                     logger.info("Failed validation based on validator")
                     s = f"Validation failed: {v}"
-                    raise ValidationError(s, True, v)
+                    raise ValidationError(s, raises=True, validator=v)
 
             logger.info(f"Postprocessing for {job.name}.run")
             job.postprocess()
@@ -655,7 +655,9 @@ class Custodian:
                         if h.raise_on_max:
                             self.run_log[-1]["handler"] = h
                             self.run_log[-1]["max_errors_per_handler"] = True
-                            raise MaxCorrectionsPerHandlerError(msg, True, h.max_num_corrections, h)
+                            raise MaxCorrectionsPerHandlerError(
+                                msg, raises=True, max_errors_per_handler=h.max_num_corrections, handler=h
+                            )
                         logger.warning(msg + " Correction not applied.")
                         continue
                     if terminate_func is not None and h.is_terminating:
@@ -914,7 +916,7 @@ class MaxCorrectionsPerJobError(CustodianError):
 class MaxCorrectionsPerHandlerError(CustodianError):
     """Error raised when the maximum allowed number of errors per handler is reached."""
 
-    def __init__(self, message, raises, max_errors_per_handler, handler):
+    def __init__(self, message: str, raises: bool, max_errors_per_handler: int, handler: ErrorHandler) -> None:
         """
         Args:
             message (str): Message passed to Exception
diff --git a/custodian/qchem/handlers.py b/custodian/qchem/handlers.py
index 28e0e510..5af315cb 100644
--- a/custodian/qchem/handlers.py
+++ b/custodian/qchem/handlers.py
@@ -126,9 +126,7 @@ class QChemErrorHandler(ErrorHandler):
             elif int(self.qcinp.rem.get("geom_opt_max_cycles", 50)) < self.geom_max_cycles:
                 self.qcinp.rem["geom_opt_max_cycles"] = self.geom_max_cycles
                 if str(self.qcinp.rem.get("geom_opt2", "none")) == "3" or self.outdata["version"] == "6":
-                    self.qcinp.geom_opt[  # pylint: disable=unsupported-assignment-operation
-                        "maxiter"
-                    ] = self.geom_max_cycles
+                    self.qcinp.geom_opt["maxiter"] = self.geom_max_cycles  # pylint: disable=unsupported-assignment-operation
                 actions.append({"geom_max_cycles:": self.geom_max_cycles})
                 if "molecule_from_last_geometry" in self.outdata:
                     self.qcinp.molecule = self.outdata.get("molecule_from_last_geometry")
@@ -194,9 +192,7 @@ class QChemErrorHandler(ErrorHandler):
 
             elif self.outdata["version"] == "6" and self.qcinp.rem.get("geom_opt_driver", "libopt3") != "optimize":
                 if self.qcinp.geom_opt["coordinates"] == "redundant":
-                    self.qcinp.geom_opt[  # pylint: disable=unsupported-assignment-operation
-                        "coordinates"
-                    ] = "delocalized"
+                    self.qcinp.geom_opt["coordinates"] = "delocalized"  # pylint: disable=unsupported-assignment-operation
                     actions.append({"coordinates": "delocalized"})
                     if self.qcinp.geom_opt.get("initial_hessian", "none") != "read":
                         self.qcinp.geom_opt["initial_hessian"] = "model"
diff --git a/custodian/vasp/handlers.py b/custodian/vasp/handlers.py
index 854ccd36..2dd68f75 100644
--- a/custodian/vasp/handlers.py
+++ b/custodian/vasp/handlers.py
@@ -78,7 +78,9 @@ class VaspErrorHandler(ErrorHandler):
         "incorrect_shift": ["Could not get correct shifts"],
         "real_optlay": ["REAL_OPTLAY: internal error", "REAL_OPT: internal ERROR"],
         "rspher": ["ERROR RSPHER"],
-        "dentet": ["DENTET"],
+        "dentet": ["DENTET"],  # reason for this warning is that the Fermi level cannot be determined accurately
+        # enough by the tetrahedron method
+        # https://vasp.at/forum/viewtopic.php?f=3&t=416&p=4047&hilit=dentet#p4047
         "too_few_bands": ["TOO FEW BANDS"],
         "triple_product": ["ERROR: the triple product of the basis vectors"],
         "rot_matrix": ["Found some non-integer element in rotation matrix", "SGRCON"],
@@ -133,7 +135,7 @@ class VaspErrorHandler(ErrorHandler):
                 lines:
 
                 ```
-                subset = list(VaspErrorHandler().error_msgs.keys())
+                subset = list(VaspErrorHandler().error_msgs)
                 subset.remove("eddrmm")
 
                 handler = VaspErrorHandler(errors_subset_to_catch=subset)
@@ -145,7 +147,7 @@ class VaspErrorHandler(ErrorHandler):
         self.output_filename = output_filename
         self.errors = set()
         self.error_count = Counter()
-        self.errors_subset_to_catch = errors_subset_to_catch or list(VaspErrorHandler.error_msgs.keys())
+        self.errors_subset_to_catch = errors_subset_to_catch or list(VaspErrorHandler.error_msgs)
         self.vtst_fixes = vtst_fixes
         self.logger = logging.getLogger(self.__class__.__name__)
 
@@ -1158,12 +1160,13 @@ class IncorrectSmearingHandler(ErrorHandler):
         return {"errors": ["IncorrectSmearing"], "actions": actions}
 
 
-class ScanMetalHandler(ErrorHandler):
+class KspacingMetalHandler(ErrorHandler):
     """
     Check if a SCAN calculation is a metal (zero bandgap) but has been run with
     a KSPACING value appropriate for semiconductors. If this occurs, this handler
     will rerun the calculation using the KSPACING setting appropriate for metals
-    (KSPACING=0.22). Note that this handler depends on values set in MPScanRelaxSet.
+    (KSPACING=0.22). Note that this handler depends on values set by set_kspacing
+    logic in MPScanRelaxSet.
     """
 
     is_monitor = False
@@ -1182,8 +1185,9 @@ class ScanMetalHandler(ErrorHandler):
         """Check for error."""
         try:
             v = Vasprun(self.output_filename)
-            # check whether bandgap is zero and tetrahedron smearing was used
-            if v.eigenvalue_band_properties[0] == 0 and v.incar.get("KSPACING", 1) > 0.22:
+            # check whether bandgap is zero and KSPACING is too large
+            # using 0 as fallback value for KSPACING so that this handler does not trigger if KSPACING is not set
+            if v.eigenvalue_band_properties[0] == 0 and v.incar.get("KSPACING", 0) > 0.22:
                 return True
         except Exception:
             pass
@@ -1208,6 +1212,20 @@ class ScanMetalHandler(ErrorHandler):
         return {"errors": ["ScanMetal"], "actions": actions}
 
 
+class ScanMetalHandler(KspacingMetalHandler):
+    """ScanMetalHandler was renamed because MP GGA workflow might also adopt kspacing
+    in the future. Keeping this alias during a deprecation period for backwards compatibility.
+    """
+
+    def __init__(self, *args, **kwargs) -> None:
+        warnings.warn(
+            "ScanMetalHandler is deprecated and will be removed in a future release. "
+            "Use KspacingMetalHandler instead.",
+            DeprecationWarning,
+        )
+        super().__init__(*args, **kwargs)
+
+
 class LargeSigmaHandler(ErrorHandler):
     """
     When ISMEAR > 0 (Methfessel-Paxton), monitor the magnitude of the entropy
@@ -1444,7 +1462,7 @@ class NonConvergingErrorHandler(ErrorHandler):
 
         # NOTE: This is the amin error handler
         # Sometimes an AMIN warning can appear with large unit cell dimensions, so we'll address it now
-        if np.max(Structure.from_file("CONTCAR").structure.lattice.abc) > 50.0 and amin > 0.01:
+        if np.max(Structure.from_file("CONTCAR").lattice.abc) > 50.0 and amin > 0.01:
             actions.append({"dict": "INCAR", "action": {"_set": {"AMIN": 0.01}}})
 
         # If a hybrid is used, do not set Algo = Fast or VeryFast. Hybrid calculations do not
diff --git a/custodian/vasp/jobs.py b/custodian/vasp/jobs.py
index 71ed3b62..91be503b 100644
--- a/custodian/vasp/jobs.py
+++ b/custodian/vasp/jobs.py
@@ -575,8 +575,8 @@ class VaspJob(Job):
                     x *= 1 + initial_strain
                 else:
                     # Sort the lattice parameter by energies.
-                    min_x = min(energies.keys(), key=lambda e: energies[e])
-                    sorted_x = sorted(energies.keys())
+                    min_x = min(energies, key=lambda e: energies[e])
+                    sorted_x = sorted(energies)
                     ind = sorted_x.index(min_x)
                     if ind == 0:
                         other = ind + 1
@@ -607,7 +607,7 @@ class VaspJob(Job):
                             try:
                                 # If there are more than 4 data points, we will
                                 # do a quadratic fit to accelerate convergence.
-                                x1 = list(energies.keys())
+                                x1 = list(energies)
                                 y1 = [energies[j] for j in x1]
                                 z1 = np.polyfit(x1, y1, 2)
                                 pp = np.poly1d(z1)
@@ -651,10 +651,10 @@ class VaspJob(Job):
                 **vasp_job_kwargs,
             )
 
-        with open("EOS.txt", "w") as f:
-            f.write(f"# {lattice_direction} energy\n")
-            for k in sorted(energies.keys()):
-                f.write(f"{k} {energies[k]}\n")
+        with open("EOS.txt", "w") as file:
+            file.write(f"# {lattice_direction} energy\n")
+            for key in sorted(energies):
+                file.write(f"{key} {energies[key]}\n")
 
     def terminate(self):
         """
diff --git a/docs/_themes/flask_theme_support.py b/docs/_themes/flask_theme_support.py
old mode 100755
new mode 100644
index 25c2df9d..b90513fb
--- a/docs/_themes/flask_theme_support.py
+++ b/docs/_themes/flask_theme_support.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # flasky extensions. flasky pygments style based on tango style
 from pygments.style import Style
 from pygments.token import (
diff --git a/pyproject.toml b/pyproject.toml
index d5726d51..40a6b42b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,3 @@
-[tool.black]
-line-length = 120
-
 [tool.ruff]
 target-version = "py38"
 line-length = 120
@@ -71,7 +68,7 @@ no_implicit_optional = false
 
 [tool.codespell]
 ignore-words-list = "ot,atomate"
-
+check-filenames = true
 
 [tool.coverage.run]
 relative_files = true
diff --git a/setup.py b/setup.py
index ce3c0606..f8a23057 100644
--- a/setup.py
+++ b/setup.py
@@ -14,16 +14,10 @@ setup(
     version="2023.10.9",
     install_requires=["monty>=2.0.6", "ruamel.yaml>=0.15.6", "sentry-sdk>=0.8.0", "psutil"],
     extras_require={
-        "vasp, nwchem, qchem": ["pymatgen>=2019.8.23"],
-        "dev": [
-            "black",
-            "mypy",
-            "pre-commit",
-            "pytest-cov",
-            "pytest-split",
-            "pytest",
-            "ruff",
-        ],
+        "vasp": ["pymatgen"],
+        "nwchem": ["pymatgen"],
+        "qchem": ["pymatgen"],
+        "dev": ["mypy", "pre-commit", "pytest-cov", "pytest", "ruff"],
     },
     package_data={},
     author="Shyue Ping Ong, William Davidson Richards, Stephen Dacek, Xiaohui Qu, Matthew Horton, Samuel M. Blau",
diff --git a/tasks.py b/tasks.py
old mode 100755
new mode 100644
index 60d75e32..411685e0
--- a/tasks.py
+++ b/tasks.py
@@ -1,8 +1,4 @@
-#!/usr/bin/env python
-"""
-Deployment file to facilitate releases of custodian.
-"""
-
+"""Deployment file to facilitate releases of custodian."""
 
 import datetime
 import glob


Test patch:
diff --git a/custodian/tests/test_custodian.py b/custodian/tests/test_custodian.py
index be30cacb..ffcc8028 100644
--- a/custodian/tests/test_custodian.py
+++ b/custodian/tests/test_custodian.py
@@ -5,7 +5,7 @@ import subprocess
 import unittest
 
 import pytest
-from ruamel import yaml
+from ruamel.yaml import YAML
 
 from custodian.custodian import (
     Custodian,
@@ -301,7 +301,7 @@ custodian_params:
 
         os.environ["TMPDIR"] = "/tmp/random"
         os.environ["PBS_NODEFILE"] = "whatever"
-        d = yaml.safe_load(spec)
+        d = YAML(typ="rt").load(spec)
         c = Custodian.from_spec(d)
         assert c.jobs[0].vasp_cmd[2] == "whatever"
         assert c.scratch_dir == "/tmp/random"
diff --git a/custodian/vasp/tests/test_handlers.py b/custodian/vasp/tests/test_handlers.py
index 5e9acbba..64a19605 100644
--- a/custodian/vasp/tests/test_handlers.py
+++ b/custodian/vasp/tests/test_handlers.py
@@ -1,28 +1,19 @@
-"""
-Created on Jun 1, 2012
-"""
-
-__author__ = "Shyue Ping Ong, Stephen Dacek"
-__copyright__ = "Copyright 2012, The Materials Project"
-__version__ = "0.1"
-__maintainer__ = "Shyue Ping Ong"
-__email__ = "shyue@mit.edu"
-__date__ = "Jun 1, 2012"
-
+"""Created on Jun 1, 2012"""
 import datetime
-import glob
 import os
 import shutil
-import unittest
+from glob import glob
 
 import pytest
 from pymatgen.io.vasp.inputs import Incar, Kpoints, Structure, VaspInput
+from pymatgen.util.testing import PymatgenTest
 
 from custodian.vasp.handlers import (
     AliasingErrorHandler,
     DriftErrorHandler,
     FrozenJobErrorHandler,
     IncorrectSmearingHandler,
+    KspacingMetalHandler,
     LargeSigmaHandler,
     LrfCommutatorHandler,
     MeshSymmetryErrorHandler,
@@ -35,197 +26,203 @@ from custodian.vasp.handlers import (
     WalltimeHandler,
 )
 
-test_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "test_files")
+__author__ = "Shyue Ping Ong, Stephen Dacek, Janosh Riebesell"
+__copyright__ = "Copyright 2012, The Materials Project"
+__version__ = "0.1"
+__maintainer__ = "Shyue Ping Ong"
+__email__ = "shyue@mit.edu"
+__date__ = "Jun 1, 2012"
 
-cwd = os.getcwd()
+TEST_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", "test_files")
+CWD = os.getcwd()
+os.environ.setdefault("PMG_VASP_PSP_DIR", TEST_DIR)
 
 
-def clean_dir():
-    for f in glob.glob("error.*.tar.gz"):
-        os.remove(f)
-    for f in glob.glob("custodian.chk.*.tar.gz"):
-        os.remove(f)
+def copy_tmp_files(tmp_path: str, *file_paths: str) -> None:
+    for file_path in file_paths:
+        src_path = f"{TEST_DIR}/{file_path}"
+        dst_path = f"{tmp_path}/{os.path.basename(file_path)}"
+        if os.path.isdir(src_path):
+            shutil.copytree(src_path, dst_path)
+        else:
+            shutil.copy(src_path, dst_path)
+    os.chdir(tmp_path)
 
 
-class VaspErrorHandlerTest(unittest.TestCase):
+class VaspErrorHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ["PMG_VASP_PSP_DIR"] = test_dir
-        os.chdir(test_dir)
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("KPOINTS", "KPOINTS.orig")
-        shutil.copy("POSCAR", "POSCAR.orig")
-        shutil.copy("CHGCAR", "CHGCAR.orig")
+        copy_tmp_files(self.tmp_path, *glob("*", root_dir=TEST_DIR))
 
     def test_frozen_job(self):
-        h = FrozenJobErrorHandler()
-        d = h.correct()
-        assert d["errors"] == ["Frozen job"]
+        handler = FrozenJobErrorHandler()
+        dct = handler.correct()
+        assert dct["errors"] == ["Frozen job"]
         assert Incar.from_file("INCAR")["ALGO"] == "Normal"
 
     def test_algotet(self):
         shutil.copy("INCAR.algo_tet_only", "INCAR")
-        h = VaspErrorHandler("vasp.algo_tet_only")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["algo_tet"]
-        assert d["actions"] == [{"action": {"_set": {"ALGO": "Fast"}}, "dict": "INCAR"}]
-        assert h.error_count["algo_tet"] == 1
+        handler = VaspErrorHandler("vasp.algo_tet_only")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["algo_tet"]
+        assert dct["actions"] == [{"action": {"_set": {"ALGO": "Fast"}}, "dict": "INCAR"}]
+        assert handler.error_count["algo_tet"] == 1
 
         # 2nd error should set ISMEAR to 0.
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["algo_tet"]
-        assert h.error_count["algo_tet"] == 2
-        assert d["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["algo_tet"]
+        assert handler.error_count["algo_tet"] == 2
+        assert dct["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
 
     def test_subspace(self):
-        h = VaspErrorHandler("vasp.subspace")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["subspacematrix"]
-        assert d["actions"] == [{"action": {"_set": {"LREAL": False}}, "dict": "INCAR"}]
+        handler = VaspErrorHandler("vasp.subspace")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["subspacematrix"]
+        assert dct["actions"] == [{"action": {"_set": {"LREAL": False}}, "dict": "INCAR"}]
 
         # 2nd error should set PREC to accurate.
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["subspacematrix"]
-        assert d["actions"] == [{"action": {"_set": {"PREC": "Accurate"}}, "dict": "INCAR"}]
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["subspacematrix"]
+        assert dct["actions"] == [{"action": {"_set": {"PREC": "Accurate"}}, "dict": "INCAR"}]
 
     def test_check_correct(self):
-        h = VaspErrorHandler("vasp.teterror")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["tet"]
-        assert d["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
-
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["tet"]
-        assert d["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
-
-        h = VaspErrorHandler("vasp.teterror", errors_subset_to_catch=["eddrmm"])
-        assert not h.check()
-
-        h = VaspErrorHandler("vasp.sgrcon")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["rot_matrix"]
-        assert {a["dict"] for a in d["actions"]} == {"KPOINTS"}
-
-        h = VaspErrorHandler("vasp.real_optlay")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["real_optlay"]
-        assert d["actions"] == [{"action": {"_set": {"LREAL": False}}, "dict": "INCAR"}]
+        handler = VaspErrorHandler("vasp.teterror")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["tet"]
+        assert dct["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
+
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["tet"]
+        assert dct["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
+
+        handler = VaspErrorHandler("vasp.teterror", errors_subset_to_catch=["eddrmm"])
+        assert not handler.check()
+
+        handler = VaspErrorHandler("vasp.sgrcon")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["rot_matrix"]
+        assert {a["dict"] for a in dct["actions"]} == {"KPOINTS"}
+
+        handler = VaspErrorHandler("vasp.real_optlay")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["real_optlay"]
+        assert dct["actions"] == [{"action": {"_set": {"LREAL": False}}, "dict": "INCAR"}]
 
     def test_mesh_symmetry(self):
-        h = MeshSymmetryErrorHandler("vasp.ibzkpt")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["mesh_symmetry"]
-        assert d["actions"] == [{"action": {"_set": {"kpoints": [[4, 4, 4]]}}, "dict": "KPOINTS"}]
+        handler = MeshSymmetryErrorHandler("vasp.ibzkpt")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["mesh_symmetry"]
+        assert dct["actions"] == [{"action": {"_set": {"kpoints": [[4, 4, 4]]}}, "dict": "KPOINTS"}]
 
     def test_brions(self):
         shutil.copy("INCAR.ibrion", "INCAR")
-        h = VaspErrorHandler("vasp.brions")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["brions"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 1
-        assert i["POTIM"] == pytest.approx(1.5)
-
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["brions"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 2
-        assert i["POTIM"] == pytest.approx(0.5)
+        handler = VaspErrorHandler("vasp.brions")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["brions"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 1
+        assert incar["POTIM"] == pytest.approx(1.5)
+
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["brions"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 2
+        assert incar["POTIM"] == pytest.approx(0.5)
 
     def test_dentet(self):
-        h = VaspErrorHandler("vasp.dentet")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["dentet"]
-        assert d["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
+        handler = VaspErrorHandler("vasp.dentet")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["dentet"]
+        assert dct["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
 
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["dentet"]
-        assert d["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["dentet"]
+        assert dct["actions"] == [{"action": {"_set": {"ISMEAR": 0, "SIGMA": 0.05}}, "dict": "INCAR"}]
 
     def test_zbrent(self):
-        h = VaspErrorHandler("vasp.zbrent")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 2
-        assert i["EDIFF"] == 1e-06
-        assert i["NELMIN"] == 8
-
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 1
-        assert i["EDIFF"] == 1e-07
-        assert i["NELMIN"] == 8
-
-        shutil.copy("INCAR.orig", "INCAR")
-        h = VaspErrorHandler("vasp.zbrent")
-        h.vtst_fixes = True
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 3
-        assert i["IOPT"] == 7
-        assert i["POTIM"] == 0
-        assert i["EDIFF"] == 1e-06
-        assert i["NELMIN"] == 8
-
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 3
-        assert i["IOPT"] == 7
-        assert i["POTIM"] == 0
-        assert i["EDIFF"] == 1e-07
-        assert i["NELMIN"] == 8
+        handler = VaspErrorHandler("vasp.zbrent")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 2
+        assert incar["EDIFF"] == 1e-06
+        assert incar["NELMIN"] == 8
+
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 1
+        assert incar["EDIFF"] == 1e-07
+        assert incar["NELMIN"] == 8
+
+        shutil.copy(f"{TEST_DIR}/INCAR", f"{self.tmp_path}/INCAR")
+        handler = VaspErrorHandler("vasp.zbrent")
+        handler.vtst_fixes = True
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 3
+        assert incar["IOPT"] == 7
+        assert incar["POTIM"] == 0
+        assert incar["EDIFF"] == 1e-06
+        assert incar["NELMIN"] == 8
+
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 3
+        assert incar["IOPT"] == 7
+        assert incar["POTIM"] == 0
+        assert incar["EDIFF"] == 1e-07
+        assert incar["NELMIN"] == 8
 
         shutil.copy("INCAR.ediff", "INCAR")
-        h = VaspErrorHandler("vasp.zbrent")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 2
-        assert i["EDIFF"] == 1e-07
-        assert i["NELMIN"] == 8
-
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["zbrent"]
-        i = Incar.from_file("INCAR")
-        assert i["IBRION"] == 1
-        assert i["EDIFF"] == 1e-08
-        assert i["NELMIN"] == 8
+        handler = VaspErrorHandler("vasp.zbrent")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 2
+        assert incar["EDIFF"] == 1e-07
+        assert incar["NELMIN"] == 8
+
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zbrent"]
+        incar = Incar.from_file("INCAR")
+        assert incar["IBRION"] == 1
+        assert incar["EDIFF"] == 1e-08
+        assert incar["NELMIN"] == 8
 
     def test_brmix(self):
-        h = VaspErrorHandler("vasp.brmix")
-        assert h.check() is True
+        handler = VaspErrorHandler("vasp.brmix")
+        assert handler.check() is True
 
         # The first (no good OUTCAR) correction, check IMIX
-        d = h.correct()
-        assert d["errors"] == ["brmix"]
+        dct = handler.correct()
+        assert dct["errors"] == ["brmix"]
         vi = VaspInput.from_directory(".")
         assert vi["INCAR"]["IMIX"] == 1
         assert os.path.exists("CHGCAR")
 
         # The next correction check Gamma and evenize
-        h.correct()
+        handler.correct()
         vi = VaspInput.from_directory(".")
         assert "IMIX" not in vi["INCAR"]
         assert os.path.exists("CHGCAR")
@@ -234,278 +231,276 @@ class VaspErrorHandlerTest(unittest.TestCase):
             assert not all_kpts_even
 
         # The next correction check ISYM and no CHGCAR
-        h.correct()
+        handler.correct()
         vi = VaspInput.from_directory(".")
         assert vi["INCAR"]["ISYM"] == 0
         assert not os.path.exists("CHGCAR")
 
         shutil.copy("INCAR.nelect", "INCAR")
-        h = VaspErrorHandler("vasp.brmix")
-        assert h.check() is False
-        d = h.correct()
-        assert d["errors"] == []
+        handler = VaspErrorHandler("vasp.brmix")
+        assert handler.check() is False
+        dct = handler.correct()
+        assert dct["errors"] == []
 
     def test_too_few_bands(self):
-        os.chdir(os.path.join(test_dir, "too_few_bands"))
+        os.chdir(os.path.join(TEST_DIR, "too_few_bands"))
         shutil.copy("INCAR", "INCAR.orig")
-        h = VaspErrorHandler("vasp.too_few_bands")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["too_few_bands"]
-        assert d["actions"] == [{"action": {"_set": {"NBANDS": 501}}, "dict": "INCAR"}]
-        clean_dir()
+        handler = VaspErrorHandler("vasp.too_few_bands")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["too_few_bands"]
+        assert dct["actions"] == [{"action": {"_set": {"NBANDS": 501}}, "dict": "INCAR"}]
         shutil.move("INCAR.orig", "INCAR")
-        os.chdir(test_dir)
+        os.chdir(TEST_DIR)
 
     def test_rot_matrix(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        subdir = os.path.join(test_dir, "poscar_error")
+        subdir = os.path.join(TEST_DIR, "poscar_error")
         os.chdir(subdir)
         shutil.copy("KPOINTS", "KPOINTS.orig")
-        h = VaspErrorHandler()
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["rot_matrix"]
+        handler = VaspErrorHandler()
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["rot_matrix"]
         os.remove(os.path.join(subdir, "error.1.tar.gz"))
         shutil.copy("KPOINTS.orig", "KPOINTS")
         os.remove("KPOINTS.orig")
 
     def test_rot_matrix_vasp6(self):
-        h = VaspErrorHandler("vasp6.sgrcon")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["rot_matrix"]
+        handler = VaspErrorHandler("vasp6.sgrcon")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["rot_matrix"]
 
     def test_coef(self):
-        h = VaspErrorHandler("vasp6.coef")
-        h.check()
-        d = h.correct()
-        assert d["actions"] == [{"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}]
+        handler = VaspErrorHandler("vasp6.coef")
+        handler.check()
+        dct = handler.correct()
+        assert dct["actions"] == [{"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}]
 
-        h = VaspErrorHandler("vasp6.coef2")
-        h.check()
-        d = h.correct()
-        assert d["actions"] == [{"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}]
+        handler = VaspErrorHandler("vasp6.coef2")
+        handler.check()
+        dct = handler.correct()
+        assert dct["actions"] == [{"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}}]
 
     def test_to_from_dict(self):
-        h = VaspErrorHandler("random_name")
-        h2 = VaspErrorHandler.from_dict(h.as_dict())
-        assert type(h2) == type(h)
+        handler = VaspErrorHandler("random_name")
+        h2 = VaspErrorHandler.from_dict(handler.as_dict())
+        assert type(h2) == type(handler)
         assert h2.output_filename == "random_name"
 
     def test_pssyevx(self):
-        h = VaspErrorHandler("vasp.pssyevx")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["pssyevx"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Normal"
+        handler = VaspErrorHandler("vasp.pssyevx")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["pssyevx"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Normal"
 
     def test_eddrmm(self):
         shutil.copy("CONTCAR.eddav_eddrmm", "CONTCAR")
-        h = VaspErrorHandler("vasp.eddrmm")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["eddrmm"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Normal"
-        assert h.correct()["errors"] == ["eddrmm"]
-        i = Incar.from_file("INCAR")
-        assert i["POTIM"] == 0.25
+        handler = VaspErrorHandler("vasp.eddrmm")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["eddrmm"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Normal"
+        assert handler.correct()["errors"] == ["eddrmm"]
+        incar = Incar.from_file("INCAR")
+        assert incar["POTIM"] == 0.25
         p = Structure.from_file("POSCAR")
         c = Structure.from_file("CONTCAR")
         assert p == c
 
     def test_nicht_konv(self):
-        h = VaspErrorHandler("vasp.nicht_konvergent")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["nicht_konv"]
-        i = Incar.from_file("INCAR")
-        assert i["LREAL"] is False
+        handler = VaspErrorHandler("vasp.nicht_konvergent")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["nicht_konv"]
+        incar = Incar.from_file("INCAR")
+        assert incar["LREAL"] is False
 
     def test_edddav(self):
         shutil.copy("CONTCAR.eddav_eddrmm", "CONTCAR")
-        h = VaspErrorHandler("vasp.edddav2")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["edddav"]
-        i = Incar.from_file("INCAR")
-        assert i["NCORE"] == 2
+        handler = VaspErrorHandler("vasp.edddav2")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["edddav"]
+        incar = Incar.from_file("INCAR")
+        assert incar["NCORE"] == 2
         p = Structure.from_file("POSCAR")
         c = Structure.from_file("CONTCAR")
         assert p == c
 
-        h = VaspErrorHandler("vasp.edddav")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["edddav"]
+        handler = VaspErrorHandler("vasp.edddav")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["edddav"]
         assert not os.path.exists("CHGCAR")
         p = Structure.from_file("POSCAR")
         c = Structure.from_file("CONTCAR")
         assert p == c
 
     def test_gradient_not_orthogonal(self):
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Fast"
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Fast"
 
         shutil.copy("INCAR.gga_all", "INCAR")
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Fast"
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Fast"
 
         shutil.copy("INCAR.gga_ialgo53", "INCAR")
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Fast"
-        assert "IALGO" not in i
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Fast"
+        assert "IALGO" not in incar
 
         shutil.copy("INCAR.hybrid_normal", "INCAR")
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "Normal"
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "Normal"
 
         shutil.copy("INCAR.hybrid_all", "INCAR")
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "All"
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "All"
 
         shutil.copy("INCAR.metagga_all", "INCAR")
-        h = VaspErrorHandler("vasp.gradient_not_orthogonal")
-        assert h.check() is True
-        assert "grad_not_orth" in h.correct()["errors"]
-        i = Incar.from_file("INCAR")
-        assert i["ALGO"] == "All"
+        handler = VaspErrorHandler("vasp.gradient_not_orthogonal")
+        assert handler.check() is True
+        assert "grad_not_orth" in handler.correct()["errors"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ALGO"] == "All"
 
     def test_rhosyg(self):
-        h = VaspErrorHandler("vasp.rhosyg")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["rhosyg"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 0.0001
-        assert h.correct()["errors"] == ["rhosyg"]
-        i = Incar.from_file("INCAR")
-        assert i["ISYM"] == 0
+        handler = VaspErrorHandler("vasp.rhosyg")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["rhosyg"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 0.0001
+        assert handler.correct()["errors"] == ["rhosyg"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISYM"] == 0
 
     def test_rhosyg_vasp6(self):
-        h = VaspErrorHandler("vasp6.rhosyg")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["rhosyg"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 0.0001
-        assert h.correct()["errors"] == ["rhosyg"]
-        i = Incar.from_file("INCAR")
-        assert i["ISYM"] == 0
+        handler = VaspErrorHandler("vasp6.rhosyg")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["rhosyg"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 0.0001
+        assert handler.correct()["errors"] == ["rhosyg"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISYM"] == 0
 
     def test_hnform(self):
-        h = VaspErrorHandler("vasp.hnform")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["hnform"]
-        i = Incar.from_file("INCAR")
-        assert i["ISYM"] == 0
+        handler = VaspErrorHandler("vasp.hnform")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["hnform"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISYM"] == 0
 
     def test_bravais(self):
-        h = VaspErrorHandler("vasp6.bravais")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["bravais"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 0.0001
+        handler = VaspErrorHandler("vasp6.bravais")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["bravais"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 0.0001
 
         shutil.copy("INCAR.symprec", "INCAR")
-        h = VaspErrorHandler("vasp6.bravais")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["bravais"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 1e-6
+        handler = VaspErrorHandler("vasp6.bravais")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["bravais"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 1e-6
 
     def test_posmap(self):
-        h = VaspErrorHandler("vasp.posmap")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["posmap"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == pytest.approx(1e-6)
+        handler = VaspErrorHandler("vasp.posmap")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["posmap"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == pytest.approx(1e-6)
 
-        assert h.check() is True
-        assert h.correct()["errors"] == ["posmap"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == pytest.approx(1e-4)
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["posmap"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == pytest.approx(1e-4)
 
     def test_posmap_vasp6(self):
-        h = VaspErrorHandler("vasp6.posmap")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["posmap"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == pytest.approx(1e-6)
+        handler = VaspErrorHandler("vasp6.posmap")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["posmap"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == pytest.approx(1e-6)
 
-        assert h.check() is True
-        assert h.correct()["errors"] == ["posmap"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == pytest.approx(1e-4)
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["posmap"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == pytest.approx(1e-4)
 
     def test_point_group(self):
-        h = VaspErrorHandler("vasp.point_group")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["point_group"]
-        i = Incar.from_file("INCAR")
-        assert i["ISYM"] == 0
+        handler = VaspErrorHandler("vasp.point_group")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["point_group"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISYM"] == 0
 
     def test_symprec_noise(self):
-        h = VaspErrorHandler("vasp.symprec_noise")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["symprec_noise"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 1e-06
+        handler = VaspErrorHandler("vasp.symprec_noise")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["symprec_noise"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 1e-06
 
     def test_dfpt_ncore(self):
-        h = VaspErrorHandler("vasp.dfpt_ncore")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["dfpt_ncore"]
+        handler = VaspErrorHandler("vasp.dfpt_ncore")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["dfpt_ncore"]
         incar = Incar.from_file("INCAR")
         assert "NPAR" not in incar
         assert "NCORE" not in incar
 
     def test_finite_difference_ncore(self):
-        h = VaspErrorHandler("vasp.fd_ncore")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["dfpt_ncore"]
+        handler = VaspErrorHandler("vasp.fd_ncore")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["dfpt_ncore"]
         incar = Incar.from_file("INCAR")
         assert "NPAR" not in incar
         assert "NCORE" not in incar
 
     def test_point_group_vasp6(self):
         # the error message is formatted differently in VASP6 compared to VASP5
-        h = VaspErrorHandler("vasp6.point_group")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["point_group"]
-        i = Incar.from_file("INCAR")
-        assert i["ISYM"] == 0
+        handler = VaspErrorHandler("vasp6.point_group")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["point_group"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISYM"] == 0
 
     def test_inv_rot_matrix_vasp6(self):
         # the error message is formatted differently in VASP6 compared to VASP5
-        h = VaspErrorHandler("vasp6.inv_rot_mat")
-        assert h.check() is True
-        assert h.correct()["errors"] == ["inv_rot_mat"]
-        i = Incar.from_file("INCAR")
-        assert i["SYMPREC"] == 1e-08
+        handler = VaspErrorHandler("vasp6.inv_rot_mat")
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["inv_rot_mat"]
+        incar = Incar.from_file("INCAR")
+        assert incar["SYMPREC"] == 1e-08
 
     def test_bzint_vasp6(self):
         # the BZINT error message is formatted differently in VASP6 compared to VASP5
-        h = VaspErrorHandler("vasp6.bzint")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["tet"]
+        handler = VaspErrorHandler("vasp6.bzint")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["tet"]
         incar = Incar.from_file("INCAR")
         assert incar["ISMEAR"] == -5
         assert incar["SIGMA"] == 0.05
-        assert d["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
+        assert dct["actions"] == [{"action": {"_set": {"kpoints": ((10, 2, 2),)}}, "dict": "KPOINTS"}]
 
-        assert h.check() is True
-        assert h.correct()["errors"] == ["tet"]
+        assert handler.check() is True
+        assert handler.correct()["errors"] == ["tet"]
         incar = Incar.from_file("INCAR")
         assert incar["ISMEAR"] == 0
         assert incar["SIGMA"] == 0.05
@@ -513,88 +508,75 @@ class VaspErrorHandlerTest(unittest.TestCase):
     def test_too_large_kspacing(self):
         shutil.copy("INCAR.kspacing", "INCAR")
         vi = VaspInput.from_directory(".")
-        h = VaspErrorHandler("vasp.teterror")
-        h.check()
-        d = h.correct()
-        assert d["errors"] == ["tet"]
-        assert d["actions"] == [{"action": {"_set": {"KSPACING": vi["INCAR"].get("KSPACING") * 0.8}}, "dict": "INCAR"}]
+        handler = VaspErrorHandler("vasp.teterror")
+        handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["tet"]
+        assert dct["actions"] == [
+            {"action": {"_set": {"KSPACING": vi["INCAR"].get("KSPACING") * 0.8}}, "dict": "INCAR"}
+        ]
 
     def test_nbands_not_sufficient(self):
-        h = VaspErrorHandler("vasp.nbands_not_sufficient")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["nbands_not_sufficient"]
-        assert d["actions"] is None
+        handler = VaspErrorHandler("vasp.nbands_not_sufficient")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["nbands_not_sufficient"]
+        assert dct["actions"] is None
 
     def test_too_few_bands_round_error(self):
         # originally there are NBANDS= 7
         # correction should increase it
         shutil.copy("INCAR.too_few_bands_round_error", "INCAR")
-        h = VaspErrorHandler("vasp.too_few_bands_round_error")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["too_few_bands"]
-        assert d["actions"] == [{"dict": "INCAR", "action": {"_set": {"NBANDS": 8}}}]
+        handler = VaspErrorHandler("vasp.too_few_bands_round_error")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["too_few_bands"]
+        assert dct["actions"] == [{"dict": "INCAR", "action": {"_set": {"NBANDS": 8}}}]
 
     def test_set_core_wf(self):
-        h = VaspErrorHandler("vasp.set_core_wf")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["set_core_wf"]
-        assert d["actions"] is None
+        handler = VaspErrorHandler("vasp.set_core_wf")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["set_core_wf"]
+        assert dct["actions"] is None
 
     def test_read_error(self):
-        h = VaspErrorHandler("vasp.read_error")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["read_error"]
-        assert d["actions"] is None
-
-    def tearDown(self):
-        os.chdir(test_dir)
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("KPOINTS.orig", "KPOINTS")
-        shutil.move("POSCAR.orig", "POSCAR")
-        shutil.move("CHGCAR.orig", "CHGCAR")
-        clean_dir()
-        os.chdir(cwd)
+        handler = VaspErrorHandler("vasp.read_error")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["read_error"]
+        assert dct["actions"] is None
 
 
-class AliasingErrorHandlerTest(unittest.TestCase):
+class AliasingErrorHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("KPOINTS", "KPOINTS.orig")
-        shutil.copy("POSCAR", "POSCAR.orig")
-        shutil.copy("CHGCAR", "CHGCAR.orig")
+        copy_tmp_files(self.tmp_path, "INCAR", "KPOINTS", "POSCAR", "CHGCAR")
 
     def test_aliasing(self):
-        os.chdir(os.path.join(test_dir, "aliasing"))
+        os.chdir(os.path.join(TEST_DIR, "aliasing"))
         shutil.copy("INCAR", "INCAR.orig")
-        h = AliasingErrorHandler("vasp.aliasing")
-        h.check()
-        d = h.correct()
+        handler = AliasingErrorHandler("vasp.aliasing")
+        handler.check()
+        dct = handler.correct()
         shutil.move("INCAR.orig", "INCAR")
-        clean_dir()
-        os.chdir(test_dir)
+        os.chdir(TEST_DIR)
 
-        assert d["errors"] == ["aliasing"]
-        assert d["actions"] == [
+        assert dct["errors"] == ["aliasing"]
+        assert dct["actions"] == [
             {"action": {"_set": {"NGX": 34}}, "dict": "INCAR"},
             {"file": "CHGCAR", "action": {"_file_delete": {"mode": "actual"}}},
             {"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}},
         ]
 
     def test_aliasing_incar(self):
-        os.chdir(os.path.join(test_dir, "aliasing"))
+        os.chdir(os.path.join(TEST_DIR, "aliasing"))
         shutil.copy("INCAR", "INCAR.orig")
-        h = AliasingErrorHandler("vasp.aliasing_incar")
-        h.check()
-        d = h.correct()
+        handler = AliasingErrorHandler("vasp.aliasing_incar")
+        handler.check()
+        dct = handler.correct()
 
-        assert d["errors"] == ["aliasing_incar"]
-        assert d["actions"] == [
+        assert dct["errors"] == ["aliasing_incar"]
+        assert dct["actions"] == [
             {"action": {"_unset": {"NGY": 1, "NGZ": 1}}, "dict": "INCAR"},
             {"file": "CHGCAR", "action": {"_file_delete": {"mode": "actual"}}},
             {"file": "WAVECAR", "action": {"_file_delete": {"mode": "actual"}}},
@@ -603,277 +585,197 @@ class AliasingErrorHandlerTest(unittest.TestCase):
         incar = Incar.from_file("INCAR.orig")
         incar["ICHARG"] = 10
         incar.write_file("INCAR")
-        d = h.correct()
-        assert d["errors"] == ["aliasing_incar"]
-        assert d["actions"] == [{"action": {"_unset": {"NGY": 1, "NGZ": 1}}, "dict": "INCAR"}]
+        dct = handler.correct()
+        assert dct["errors"] == ["aliasing_incar"]
+        assert dct["actions"] == [{"action": {"_unset": {"NGY": 1, "NGZ": 1}}, "dict": "INCAR"}]
 
         shutil.move("INCAR.orig", "INCAR")
-        clean_dir()
-        os.chdir(test_dir)
-
-    def tearDown(self):
-        os.chdir(test_dir)
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("KPOINTS.orig", "KPOINTS")
-        shutil.move("POSCAR.orig", "POSCAR")
-        shutil.move("CHGCAR.orig", "CHGCAR")
-        clean_dir()
-        os.chdir(cwd)
+        os.chdir(TEST_DIR)
 
 
-class UnconvergedErrorHandlerTest(unittest.TestCase):
+class UnconvergedErrorHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "unconverged")
-        os.chdir(subdir)
-
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("KPOINTS", "KPOINTS.orig")
-        shutil.copy("POSCAR", "POSCAR.orig")
-        shutil.copy("CONTCAR", "CONTCAR.orig")
+        copy_tmp_files(self.tmp_path, *glob("unconverged/*", root_dir=TEST_DIR))
 
     def test_check_correct_electronic(self):
         shutil.copy("vasprun.xml.electronic", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "Normal"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {
+            "actions": [{"action": {"_set": {"ALGO": "Normal"}}, "dict": "INCAR"}],
+            "errors": ["Unconverged"],
+        }
 
         shutil.copy("vasprun.xml.electronic_veryfast", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "Fast"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "Fast"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
         shutil.copy("vasprun.xml.electronic_normal", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
         shutil.copy("vasprun.xml.electronic_metagga_fast", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
         shutil.copy("vasprun.xml.electronic_hybrid_fast", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
         shutil.copy("vasprun.xml.electronic_hybrid_all", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert [{"dict": "INCAR", "action": {"_set": {"ALGO": "Damped", "TIME": 0.5}}}] == d["actions"]
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert [{"dict": "INCAR", "action": {"_set": {"ALGO": "Damped", "TIME": 0.5}}}] == dct["actions"]
 
     def test_check_correct_electronic_repeat(self):
         shutil.copy("vasprun.xml.electronic2", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
     def test_check_correct_ionic(self):
         shutil.copy("vasprun.xml.ionic", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
 
     def test_check_correct_scan(self):
         shutil.copy("vasprun.xml.scan", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert {"dict": "INCAR", "action": {"_set": {"ALGO": "All"}}} in d["actions"]
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert {"dict": "INCAR", "action": {"_set": {"ALGO": "All"}}} in dct["actions"]
 
     def test_amin(self):
         shutil.copy("vasprun.xml.electronic_amin", "vasprun.xml")
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert [{"dict": "INCAR", "action": {"_set": {"AMIN": 0.01}}}] == d["actions"]
-        os.remove("vasprun.xml")
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert [{"dict": "INCAR", "action": {"_set": {"AMIN": 0.01}}}] == dct["actions"]
 
     def test_to_from_dict(self):
-        h = UnconvergedErrorHandler("random_name.xml")
-        h2 = UnconvergedErrorHandler.from_dict(h.as_dict())
+        handler = UnconvergedErrorHandler("random_name.xml")
+        h2 = UnconvergedErrorHandler.from_dict(handler.as_dict())
         assert type(h2) == UnconvergedErrorHandler
         assert h2.output_filename == "random_name.xml"
 
     def test_correct_normal_with_condition(self):
         shutil.copy("vasprun.xml.electronic_normal", "vasprun.xml")  # Reuse an existing file
-        h = UnconvergedErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Unconverged"]
-        assert d == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
-        os.remove("vasprun.xml")
-
-    @classmethod
-    def tearDown(cls):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("KPOINTS.orig", "KPOINTS")
-        shutil.move("POSCAR.orig", "POSCAR")
-        shutil.move("CONTCAR.orig", "CONTCAR")
-        clean_dir()
-        os.chdir(cwd)
+        handler = UnconvergedErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Unconverged"]
+        assert dct == {"actions": [{"action": {"_set": {"ALGO": "All"}}, "dict": "INCAR"}], "errors": ["Unconverged"]}
 
 
-class IncorrectSmearingHandlerTest(unittest.TestCase):
+class IncorrectSmearingHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "scan_metal")
-        os.chdir(subdir)
-
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("vasprun.xml", "vasprun.xml.orig")
+        copy_tmp_files(self.tmp_path, "scan_metal/INCAR", "scan_metal/vasprun.xml")
 
     def test_check_correct_scan_metal(self):
-        h = IncorrectSmearingHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["IncorrectSmearing"]
-        assert Incar.from_file("INCAR")["ISMEAR"] == 2
-        assert Incar.from_file("INCAR")["SIGMA"] == 0.2
-        os.remove("vasprun.xml")
-
-    @classmethod
-    def tearDown(cls):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("vasprun.xml.orig", "vasprun.xml")
-        clean_dir()
-        os.chdir(cwd)
+        handler = IncorrectSmearingHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["IncorrectSmearing"]
+        incar = Incar.from_file("INCAR")
+        assert incar["ISMEAR"] == 2
+        assert incar["SIGMA"] == 0.2
 
 
-class IncorrectSmearingHandlerStaticTest(unittest.TestCase):
+class IncorrectSmearingHandlerStaticTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "static_smearing")
-        os.chdir(subdir)
-
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("vasprun.xml", "vasprun.xml.orig")
+        copy_tmp_files(self.tmp_path, "static_smearing/INCAR", "static_smearing/vasprun.xml")
 
     def test_check_correct_scan_metal(self):
-        h = IncorrectSmearingHandler()
-        assert not h.check()
+        handler = IncorrectSmearingHandler()
+        assert not handler.check()
 
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("vasprun.xml.orig", "vasprun.xml")
-        clean_dir()
-        os.chdir(cwd)
 
-
-class IncorrectSmearingHandlerFermiTest(unittest.TestCase):
+class IncorrectSmearingHandlerFermiTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "fermi_smearing")
-        os.chdir(subdir)
-
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("vasprun.xml", "vasprun.xml.orig")
+        copy_tmp_files(self.tmp_path, "fermi_smearing/INCAR", "fermi_smearing/vasprun.xml")
 
     def test_check_correct_scan_metal(self):
-        h = IncorrectSmearingHandler()
-        assert not h.check()
-
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("vasprun.xml.orig", "vasprun.xml")
-        clean_dir()
-        os.chdir(cwd)
+        handler = IncorrectSmearingHandler()
+        assert not handler.check()
 
 
-class ScanMetalHandlerTest(unittest.TestCase):
+class KspacingMetalHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "scan_metal")
-        os.chdir(subdir)
-
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("vasprun.xml", "vasprun.xml.orig")
+        copy_tmp_files(self.tmp_path, "scan_metal/INCAR", "scan_metal/vasprun.xml")
 
     def test_check_correct_scan_metal(self):
-        h = ScanMetalHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["ScanMetal"]
+        handler = KspacingMetalHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["ScanMetal"]
         assert Incar.from_file("INCAR")["KSPACING"] == 0.22
         os.remove("vasprun.xml")
 
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("vasprun.xml.orig", "vasprun.xml")
-        clean_dir()
-        os.chdir(cwd)
+    def test_check_with_non_kspacing_wf(self):
+        os.chdir(TEST_DIR)
+        shutil.copy("INCAR", f"{self.tmp_path}/INCAR")
+        shutil.copy("vasprun.xml", f"{self.tmp_path}/vasprun.xml")
+        handler = KspacingMetalHandler(output_filename=f"{self.tmp_path}/vasprun.xml")
+        assert handler.check() is False
+        os.chdir(os.path.join(TEST_DIR, "scan_metal"))
 
+        # TODO (@janosh 2023-11-03) remove when ending ScanMetalHandler deprecation period
+        assert issubclass(ScanMetalHandler, KspacingMetalHandler)
 
-class LargeSigmaHandlerTest(unittest.TestCase):
-    def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        subdir = os.path.join(test_dir, "large_sigma")
-        os.chdir(subdir)
 
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("vasprun.xml", "vasprun.xml.orig")
+class LargeSigmaHandlerTest(PymatgenTest):
+    def setUp(self):
+        copy_tmp_files(
+            self.tmp_path, "large_sigma/INCAR", "large_sigma/vasprun.xml", "large_sigma/OUTCAR", "large_sigma/POSCAR"
+        )
 
     def test_check_correct_large_sigma(self):
-        h = LargeSigmaHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["LargeSigma"]
+        handler = LargeSigmaHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["LargeSigma"]
         assert Incar.from_file("INCAR")["SIGMA"] == 1.44
-        os.remove("vasprun.xml")
-
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("vasprun.xml.orig", "vasprun.xml")
-        clean_dir()
-        os.chdir(cwd)
+        assert os.path.isfile("vasprun.xml")
 
 
-class ZpotrfErrorHandlerTest(unittest.TestCase):
+class ZpotrfErrorHandlerTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        os.chdir("zpotrf")
-        shutil.copy("POSCAR", "POSCAR.orig")
-        shutil.copy("INCAR", "INCAR.orig")
+        copy_tmp_files(
+            self.tmp_path,
+            "zpotrf/INCAR",
+            "zpotrf/POSCAR",
+            "zpotrf/OSZICAR.empty",
+            "zpotrf/vasp.out",
+            "zpotrf/OSZICAR.one_step",
+        )
 
     def test_first_step(self):
         shutil.copy("OSZICAR.empty", "OSZICAR")
-        s1 = Structure.from_file("POSCAR.orig")
-        h = VaspErrorHandler("vasp.out")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["zpotrf"]
+        s1 = Structure.from_file("POSCAR")
+        handler = VaspErrorHandler("vasp.out")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["zpotrf"]
         s2 = Structure.from_file("POSCAR")
         # NOTE (@janosh on 2023-09-10) next code line used to be:
         # assert s2.volume == pytest.approx(s1.volume * 1.2**3)
@@ -883,11 +785,11 @@ class ZpotrfErrorHandlerTest(unittest.TestCase):
 
     def test_potim_correction(self):
         shutil.copy("OSZICAR.one_step", "OSZICAR")
-        s1 = Structure.from_file("POSCAR.orig")
-        h = VaspErrorHandler("vasp.out")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["zpotrf"]
+        s1 = Structure.from_file("POSCAR")
+        handler = VaspErrorHandler("vasp.out")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["zpotrf"]
         s2 = Structure.from_file("POSCAR")
         assert s2.volume == pytest.approx(s1.volume)
         assert s1.volume == pytest.approx(64.3462)
@@ -895,106 +797,89 @@ class ZpotrfErrorHandlerTest(unittest.TestCase):
 
     def test_static_run_correction(self):
         shutil.copy("OSZICAR.empty", "OSZICAR")
-        s1 = Structure.from_file("POSCAR.orig")
+        s1 = Structure.from_file("POSCAR")
         incar = Incar.from_file("INCAR")
 
         # Test for NSW 0
         incar.update({"NSW": 0})
         incar.write_file("INCAR")
-        h = VaspErrorHandler("vasp.out")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["zpotrf"]
+        handler = VaspErrorHandler("vasp.out")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["zpotrf"]
         s2 = Structure.from_file("POSCAR")
         assert s2.volume == pytest.approx(s1.volume)
         assert s2.volume == pytest.approx(64.346221)
         assert Incar.from_file("INCAR")["ISYM"] == 0
 
-    def tearDown(self):
-        os.chdir(test_dir)
-        os.chdir("zpotrf")
-        shutil.move("POSCAR.orig", "POSCAR")
-        shutil.move("INCAR.orig", "INCAR")
-        os.remove("OSZICAR")
-        clean_dir()
-        os.chdir(cwd)
-
 
-class ZpotrfErrorHandlerSmallTest(unittest.TestCase):
+class ZpotrfErrorHandlerSmallTest(PymatgenTest):
     def setUp(self):
-        os.environ.setdefault("PMG_VASP_PSP_DIR", test_dir)
-        os.chdir(test_dir)
-        os.chdir("zpotrf_small")
-        shutil.copy("POSCAR", "POSCAR.orig")
-        shutil.copy("INCAR", "INCAR.orig")
+        copy_tmp_files(
+            self.tmp_path,
+            "zpotrf_small/INCAR",
+            "zpotrf_small/POSCAR",
+            "zpotrf_small/OSZICAR.empty",
+            "zpotrf_small/vasp.out",
+        )
 
     def test_small(self):
-        h = VaspErrorHandler("vasp.out")
+        handler = VaspErrorHandler("vasp.out")
         shutil.copy("OSZICAR.empty", "OSZICAR")
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["zpotrf"]
-        assert d["actions"] == [
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["zpotrf"]
+        assert dct["actions"] == [
             {"dict": "INCAR", "action": {"_set": {"NCORE": 1}}},
             {"dict": "INCAR", "action": {"_unset": {"NPAR": 1}}},
         ]
 
-    def tearDown(self):
-        os.chdir(test_dir)
-        os.chdir("zpotrf_small")
-        shutil.move("POSCAR.orig", "POSCAR")
-        shutil.move("INCAR.orig", "INCAR")
-        os.remove("OSZICAR")
-        clean_dir()
-        os.chdir(cwd)
-
 
-class WalltimeHandlerTest(unittest.TestCase):
+class WalltimeHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(os.path.join(test_dir, "postprocess"))
-        if "CUSTODIAN_WALLTIME_START" in os.environ:
-            os.environ.pop("CUSTODIAN_WALLTIME_START")
+        os.chdir(f"{TEST_DIR}/postprocess")
+        os.environ.pop("CUSTODIAN_WALLTIME_START", None)
 
     def test_walltime_start(self):
         # checks the walltime handlers starttime initialization
-        h = WalltimeHandler(wall_time=3600)
-        new_starttime = h.start_time
+        handler = WalltimeHandler(wall_time=3600)
+        new_starttime = handler.start_time
         assert os.environ.get("CUSTODIAN_WALLTIME_START") == new_starttime.strftime("%a %b %d %H:%M:%S UTC %Y")
         # Test that walltime persists if new handler is created
-        h = WalltimeHandler(wall_time=3600)
+        handler = WalltimeHandler(wall_time=3600)
         assert os.environ.get("CUSTODIAN_WALLTIME_START") == new_starttime.strftime("%a %b %d %H:%M:%S UTC %Y")
 
     def test_check_and_correct(self):
         # Try a 1 hr wall time with a 2 min buffer
-        h = WalltimeHandler(wall_time=3600, buffer_time=120)
-        assert not h.check()
+        handler = WalltimeHandler(wall_time=3600, buffer_time=120)
+        assert not handler.check()
 
         # This makes sure the check returns True when the time left is less
         # than the buffer time.
-        h.start_time = datetime.datetime.now() - datetime.timedelta(minutes=59)
-        assert h.check()
+        handler.start_time = datetime.datetime.now() - datetime.timedelta(minutes=59)
+        assert handler.check()
 
         # This makes sure the check returns True when the time left is less
         # than 3 x the average time per ionic step. We have a 62 min wall
         # time, a very short buffer time, but the start time was 62 mins ago
-        h = WalltimeHandler(wall_time=3720, buffer_time=10)
-        h.start_time = datetime.datetime.now() - datetime.timedelta(minutes=62)
-        assert h.check()
+        handler = WalltimeHandler(wall_time=3720, buffer_time=10)
+        handler.start_time = datetime.datetime.now() - datetime.timedelta(minutes=62)
+        assert handler.check()
 
         # Test that the STOPCAR is written correctly.
-        h.correct()
+        handler.correct()
         with open("STOPCAR") as f:
             content = f.read()
             assert content == "LSTOP = .TRUE."
         os.remove("STOPCAR")
 
-        h = WalltimeHandler(wall_time=3600, buffer_time=120, electronic_step_stop=True)
+        handler = WalltimeHandler(wall_time=3600, buffer_time=120, electronic_step_stop=True)
 
-        assert not h.check()
-        h.start_time = datetime.datetime.now() - datetime.timedelta(minutes=59)
-        assert h.check()
+        assert not handler.check()
+        handler.start_time = datetime.datetime.now() - datetime.timedelta(minutes=59)
+        assert handler.check()
 
-        h.correct()
+        handler.correct()
         with open("STOPCAR") as f:
             content = f.read()
             assert content == "LABORT = .TRUE."
@@ -1002,56 +887,41 @@ class WalltimeHandlerTest(unittest.TestCase):
 
     @classmethod
     def tearDown(cls):
-        if "CUSTODIAN_WALLTIME_START" in os.environ:
-            os.environ.pop("CUSTODIAN_WALLTIME_START")
-        os.chdir(cwd)
+        os.environ.pop("CUSTODIAN_WALLTIME_START", None)
+        os.chdir(CWD)
 
 
-class PositiveEnergyHandlerTest(unittest.TestCase):
+class PositiveEnergyHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(test_dir)
-        self.subdir = os.path.join(test_dir, "positive_energy")
-        os.chdir(self.subdir)
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("POSCAR", "POSCAR.orig")
+        copy_tmp_files(self.tmp_path, "positive_energy/INCAR", "positive_energy/POSCAR", "positive_energy/OSZICAR")
 
     def test_check_correct(self):
-        h = PositiveEnergyErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["Positive energy"]
+        handler = PositiveEnergyErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["Positive energy"]
 
-        os.remove(os.path.join(self.subdir, "error.1.tar.gz"))
+        assert os.path.isfile("error.1.tar.gz")
 
         incar = Incar.from_file("INCAR")
 
         assert incar["ALGO"] == "Normal"
 
-    @classmethod
-    def tearDownClass(cls):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("POSCAR.orig", "POSCAR")
-        os.chdir(cwd)
-
 
-class PotimHandlerTest(unittest.TestCase):
+class PotimHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(test_dir)
-        self.subdir = os.path.join(test_dir, "potim")
-        os.chdir(self.subdir)
-        shutil.copy("INCAR", "INCAR.orig")
-        shutil.copy("POSCAR", "POSCAR.orig")
+        copy_tmp_files(self.tmp_path, "potim/INCAR", "potim/POSCAR", "potim/OSZICAR")
 
     def test_check_correct(self):
         incar = Incar.from_file("INCAR")
         original_potim = incar["POTIM"]
 
-        h = PotimErrorHandler()
-        assert h.check()
-        d = h.correct()
-        assert d["errors"] == ["POTIM"]
+        handler = PotimErrorHandler()
+        assert handler.check()
+        dct = handler.correct()
+        assert dct["errors"] == ["POTIM"]
 
-        os.remove(os.path.join(self.subdir, "error.1.tar.gz"))
+        assert os.path.isfile("error.1.tar.gz")
 
         incar = Incar.from_file("INCAR")
         new_potim = incar["POTIM"]
@@ -1059,122 +929,82 @@ class PotimHandlerTest(unittest.TestCase):
         assert original_potim == new_potim
         assert incar["IBRION"] == 3
 
-    @classmethod
-    def tearDownClass(cls):
-        shutil.move("INCAR.orig", "INCAR")
-        shutil.move("POSCAR.orig", "POSCAR")
-        os.chdir(cwd)
 
-
-class LrfCommHandlerTest(unittest.TestCase):
+class LrfCommHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(test_dir)
-        os.chdir("lrf_comm")
-        for f in ["INCAR", "OUTCAR", "std_err.txt"]:
-            shutil.copy(f, f + ".orig")
+        copy_tmp_files(self.tmp_path, "lrf_comm/INCAR", "lrf_comm/OUTCAR", "lrf_comm/std_err.txt")
 
     def test_lrf_comm(self):
-        h = LrfCommutatorHandler("std_err.txt")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["lrf_comm"]
+        handler = LrfCommutatorHandler("std_err.txt")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["lrf_comm"]
         vi = VaspInput.from_directory(".")
         assert vi["INCAR"]["LPEAD"] is True
 
-    def tearDown(self):
-        os.chdir(test_dir)
-        os.chdir("lrf_comm")
-        for f in ["INCAR", "OUTCAR", "std_err.txt"]:
-            shutil.move(f + ".orig", f)
-        clean_dir()
-        os.chdir(cwd)
-
 
-class KpointsTransHandlerTest(unittest.TestCase):
+class KpointsTransHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(test_dir)
-        shutil.copy("KPOINTS", "KPOINTS.orig")
+        copy_tmp_files(self.tmp_path, "KPOINTS", "std_err.txt.kpoints_trans")
 
     def test_kpoints_trans(self):
-        h = StdErrHandler("std_err.txt.kpoints_trans")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["kpoints_trans"]
-        assert d["actions"] == [{"action": {"_set": {"kpoints": [[4, 4, 4]]}}, "dict": "KPOINTS"}]
-
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["kpoints_trans"]
-        assert d["actions"] == []  # don't correct twice
+        handler = StdErrHandler("std_err.txt.kpoints_trans")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["kpoints_trans"]
+        assert dct["actions"] == [{"action": {"_set": {"kpoints": [[4, 4, 4]]}}, "dict": "KPOINTS"}]
 
-    def tearDown(self):
-        shutil.move("KPOINTS.orig", "KPOINTS")
-        clean_dir()
-        os.chdir(cwd)
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["kpoints_trans"]
+        assert dct["actions"] == []  # don't correct twice
 
 
-class OutOfMemoryHandlerTest(unittest.TestCase):
+class OutOfMemoryHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(test_dir)
-        shutil.copy("INCAR", "INCAR.orig")
+        copy_tmp_files(self.tmp_path, "INCAR", "std_err.txt.oom")
 
     def test_oom(self):
         vi = VaspInput.from_directory(".")
         from custodian.vasp.interpreter import VaspModder
 
         VaspModder(vi=vi).apply_actions([{"dict": "INCAR", "action": {"_set": {"KPAR": 4}}}])
-        h = StdErrHandler("std_err.txt.oom")
-        assert h.check() is True
-        d = h.correct()
-        assert d["errors"] == ["out_of_memory"]
-        assert d["actions"] == [{"dict": "INCAR", "action": {"_set": {"KPAR": 2}}}]
-
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        clean_dir()
-        os.chdir(cwd)
+        handler = StdErrHandler("std_err.txt.oom")
+        assert handler.check() is True
+        dct = handler.correct()
+        assert dct["errors"] == ["out_of_memory"]
+        assert dct["actions"] == [{"dict": "INCAR", "action": {"_set": {"KPAR": 2}}}]
 
 
-class DriftErrorHandlerTest(unittest.TestCase):
+class DriftErrorHandlerTest(PymatgenTest):
     def setUp(self):
-        os.chdir(os.path.abspath(test_dir))
-        os.chdir("drift")
-        shutil.copy("INCAR", "INCAR.orig")
+        copy_tmp_files(self.tmp_path, "INCAR", "drift/OUTCAR", "drift/CONTCAR")
 
     def test_check(self):
-        h = DriftErrorHandler(max_drift=0.05, to_average=11)
-        assert not h.check()
+        handler = DriftErrorHandler(max_drift=0.05, to_average=11)
+        assert not handler.check()
 
-        h = DriftErrorHandler(max_drift=0.05)
-        assert not h.check()
+        handler = DriftErrorHandler(max_drift=0.05)
+        assert not handler.check()
 
-        h = DriftErrorHandler(max_drift=0.0001)
-        assert not h.check()
+        handler = DriftErrorHandler(max_drift=0.0001)
+        assert not handler.check()
 
         incar = Incar.from_file("INCAR")
         incar["EDIFFG"] = -0.01
         incar.write_file("INCAR")
 
-        h = DriftErrorHandler(max_drift=0.0001)
-        assert h.check()
+        handler = DriftErrorHandler(max_drift=0.0001)
+        assert handler.check()
 
-        h = DriftErrorHandler()
-        h.check()
-        assert h.max_drift == 0.01
-
-        clean_dir()
+        handler = DriftErrorHandler()
+        handler.check()
+        assert handler.max_drift == 0.01
 
     def test_correct(self):
-        h = DriftErrorHandler(max_drift=0.0001, enaug_multiply=2)
-        h.check()
-        h.correct()
+        handler = DriftErrorHandler(max_drift=0.0001, enaug_multiply=2)
+        handler.check()
+        handler.correct()
         incar = Incar.from_file("INCAR")
         assert incar.get("PREC") == "High"
         assert incar.get("ENAUG", 0) == incar.get("ENCUT", 2) * 2
-
-        clean_dir()
-
-    def tearDown(self):
-        shutil.move("INCAR.orig", "INCAR")
-        clean_dir()
-        os.chdir(cwd)
diff --git a/test_files/INCAR b/test_files/INCAR
index 3c588c38..ce2120d0 100644
--- a/test_files/INCAR
+++ b/test_files/INCAR
@@ -21,4 +21,4 @@ NELMIN = 6
 NPAR = 1
 NSW = 99
 SIGMA = 0.05
-SYMPREC = 1e-5
\ No newline at end of file
+SYMPREC = 1e-5