# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """Test handling of ``pyproject.toml`` configuration""" import os from typing import cast, Optional, Union from pathlib import Path from twisted.trial.unittest import TestCase from incremental import _load_pyproject_toml, _IncrementalConfig class VerifyPyprojectDotTomlTests(TestCase): """Test the `_load_pyproject_toml` helper function""" def _loadToml( self, toml: str, *, path: Union[Path, str, None] = None ) -> Optional[_IncrementalConfig]: """ Read a TOML snipped from a temporary file with `_load_pyproject_toml` @param toml: TOML content of the temporary file @param path: Path to which the TOML is written """ path_: str if path is None: path_ = self.mktemp() # type: ignore else: path_ = str(path) with open(path_, "w") as f: f.write(toml) try: return _load_pyproject_toml(path_) except Exception as e: if hasattr(e, "add_note"): e.add_note( # type: ignore[attr-defined] f"While loading:\n\n{toml}" ) # pragma: no coverage raise def test_fileNotFound(self): """ An absent ``pyproject.toml`` file produces no result unless there is opt-in. """ path = os.path.join(cast(str, self.mktemp()), "pyproject.toml") self.assertRaises(FileNotFoundError, _load_pyproject_toml, path) def test_brokenToml(self): """ Syntactially invalid TOML produces an exception. The specific exception varies by the underlying TOML library. """ toml = '[project]\nname = "abc' # Truncated string. self.assertRaises(Exception, self._loadToml, toml) def test_nameMissing(self): """ `ValueError` is raised when we can't extract the project name. """ for toml in [ "\n", "[tool.notincremental]\n", "[project]\n", "[tool.incremental]\n", "[project]\n[tool.incremental]\n", ]: self.assertRaises(ValueError, self._loadToml, toml) def test_nameInvalidOptIn(self): """ `TypeError` is raised when the project name isn't a string. """ for toml in [ "[project]\nname = false\n", "[tool.incremental]\nname = -1\n", "[tool.incremental]\n[project]\nname = 1.0\n", ]: with self.assertRaisesRegex(TypeError, "The project name must be a string"): self._loadToml(toml) def test_toolIncrementalInvalid(self): """ `ValueError` is raised when the ``[tool]`` or ``[tool.incremental]`` isn't a table. """ for toml in [ "tool = false\n", "[tool]\nincremental = false\n", "[tool]\nincremental = 123\n", "[tool]\nincremental = null\n", ]: self.assertRaises(ValueError, self._loadToml, toml) def test_toolIncrementalUnexpecteKeys(self): """ Raise `ValueError` when the ``[tool.incremental]`` section contains keys other than ``"name"`` """ for toml in [ "[tool.incremental]\nfoo = false\n", '[tool.incremental]\nname = "OK"\nother = false\n', ]: self.assertRaises(ValueError, self._loadToml, toml) def test_setuptoolsOptIn(self): """ The package has opted-in to Incremental version management when the ``[tool.incremental]`` section is a dict. The project name is taken from ``[tool.incremental] name`` or ``[project] name``. """ root = Path(self.mktemp()) pkg = root / "src" / "foo" pkg.mkdir(parents=True) for toml in [ '[project]\nname = "Foo"\n[tool.incremental]\n', '[tool.incremental]\nname = "Foo"\n', ]: config = self._loadToml(toml, path=root / "pyproject.toml") self.assertEqual( config, _IncrementalConfig( opt_in=True, package="Foo", path=str(pkg), ), ) def test_packagePathRequired(self): """ Raise `ValueError` when the package root can't be resolved. """ root = Path(self.mktemp()) root.mkdir() # Contains no package directory. with self.assertRaisesRegex(ValueError, "Can't find the directory of project "): self._loadToml( '[project]\nname = "foo"\n', path=root / "pyproject.toml", ) def test_noToolIncrementalSection(self): """ The ``[tool.incremental]`` table is not strictly required, but its ``opt_in=False`` indicates its absence. """ root = Path(self.mktemp()) pkg = root / "src" / "foo" pkg.mkdir(parents=True) config = self._loadToml( '[project]\nname = "Foo"\n', path=root / "pyproject.toml", ) self.assertEqual( config, _IncrementalConfig( opt_in=False, package="Foo", path=str(pkg), ), )