Add an implicit-pkg-path-as-logger-name test
A bit of test driven dev to anticipate support  of `.log.get_logger()`
usage such that it can be called from arbitrary sub-modules, themselves
embedded in arbitrary sub-pkgs, of some project; the when not provided,
the `sub_name` passed to the `Logger.getChild(<sub_name>)` will be set
as the sub-pkg path "down to" the calling module.
IOW if you call something like,
`log = tractor.log.get_logger(pkg_name='mypylib')`
from some `submod.py` in a project-dir that looks like,
mypylib/
  mod.py
  subpkg/
    submod.py  <- calling module
the `log: StackLevelAdapter` child-`Logger` instance will have a
`.name: str = 'mypylib.subpkg'`, discluding the `submod` part since this
already rendered as the `{filename}` header in `log.LOG_FORMAT`.
Previously similar behaviour would be obtained by passing
`get_logger(name=__name__)` in the calling module and so much so it
motivated me to make this the default, presuming we can introspect for
the info.
Impl deats,
- duplicated a `load_module_from_path()` from `modden` to load the
  `testdir` rendered py project dir from its path.
 |_should prolly factor it down to this lib anyway bc we're going to
   need it for hot code reload? (well that and `watchfiles` Bp)
- in each of `mod.py` and `submod.py` render the `get_logger()` code
  sin `name`, expecting the (coming shortly) implicit introspection
  feat to do this.
- do `.name` and `.parent` checks against expected sub-logger values
  from `StackLevelAdapter.logger.getChildren()`.
			
			
				log_sys_testing
			
			
		
							parent
							
								
									d2282f4275
								
							
						
					
					
						commit
						e8f2dfc088
					
				|  | @ -2,16 +2,29 @@ | ||||||
| `tractor.log`-wrapping unit tests. | `tractor.log`-wrapping unit tests. | ||||||
| 
 | 
 | ||||||
| ''' | ''' | ||||||
|  | import importlib | ||||||
|  | from pathlib import Path | ||||||
|  | import shutil | ||||||
|  | import sys | ||||||
|  | from types import ModuleType | ||||||
|  | 
 | ||||||
|  | import pytest | ||||||
| import tractor | import tractor | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_root_pkg_not_duplicated(): | def test_root_pkg_not_duplicated_in_logger_name(): | ||||||
|  |     ''' | ||||||
|  |     When both `_root_name` and `name` are passed and they have | ||||||
|  |     a common `<root_name>.< >` prefix, ensure that it is not | ||||||
|  |     duplicated in the child's `StackLevelAdapter.name: str`. | ||||||
| 
 | 
 | ||||||
|  |     ''' | ||||||
|     project_name: str = 'pylib' |     project_name: str = 'pylib' | ||||||
|     pkg_path: str = 'pylib.subpkg.mod' |     pkg_path: str = 'pylib.subpkg.mod' | ||||||
| 
 | 
 | ||||||
|     log = tractor.log.get_logger( |     proj_log = tractor.log.get_logger( | ||||||
|         _root_name=project_name, |         _root_name=project_name, | ||||||
|  |         mk_sublog=False, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     sublog = tractor.log.get_logger( |     sublog = tractor.log.get_logger( | ||||||
|  | @ -19,11 +32,111 @@ def test_root_pkg_not_duplicated(): | ||||||
|         name=pkg_path, |         name=pkg_path, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     assert log is not sublog |     assert proj_log is not sublog | ||||||
|     assert sublog.name.count(log.name) == 1 |     assert sublog.name.count(proj_log.name) == 1 | ||||||
|     assert 'mod' not in sublog.name |     assert 'mod' not in sublog.name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # ?TODO, move this into internal libs? | ||||||
|  | # -[ ] we already use it in `modden.config._pymod` as well | ||||||
|  | def load_module_from_path( | ||||||
|  |     path: Path, | ||||||
|  |     module_name: str|None = None, | ||||||
|  | ) -> ModuleType: | ||||||
|  |     ''' | ||||||
|  |     Taken from SO, | ||||||
|  |     https://stackoverflow.com/a/67208147 | ||||||
|  | 
 | ||||||
|  |     which is based on stdlib docs, | ||||||
|  |     https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly | ||||||
|  | 
 | ||||||
|  |     ''' | ||||||
|  |     module_name = module_name or path.stem | ||||||
|  |     spec = importlib.util.spec_from_file_location( | ||||||
|  |         module_name, | ||||||
|  |         str(path), | ||||||
|  |     ) | ||||||
|  |     module = importlib.util.module_from_spec(spec) | ||||||
|  |     sys.modules[module_name] = module | ||||||
|  |     spec.loader.exec_module(module) | ||||||
|  |     return module | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_implicit_mod_name_applied_for_child( | ||||||
|  |     testdir: pytest.Pytester, | ||||||
|  | ): | ||||||
|  |     ''' | ||||||
|  |     Verify that when `.log.get_logger(pkg_name='pylib')` is called | ||||||
|  |     from a given sub-mod from within the `pylib` pkg-path, we | ||||||
|  |     implicitly set the equiv of `name=__name__` from the caller's | ||||||
|  |     module. | ||||||
|  | 
 | ||||||
|  |     ''' | ||||||
|  |     proj_name: str = 'snakelib' | ||||||
|  |     mod_code: str = ( | ||||||
|  |         f'import tractor\n' | ||||||
|  |         f'\n' | ||||||
|  |         f'log = tractor.log.get_logger(_root_name="{proj_name}")\n' | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     # create a sub-module for each pkg layer | ||||||
|  |     _lib = testdir.mkpydir(proj_name) | ||||||
|  |     pkg: Path = Path(_lib) | ||||||
|  |     subpkg: Path = pkg / 'subpkg' | ||||||
|  |     subpkg.mkdir() | ||||||
|  | 
 | ||||||
|  |     pkgmod: Path = subpkg / "__init__.py" | ||||||
|  |     pkgmod.touch() | ||||||
|  | 
 | ||||||
|  |     _submod: Path = testdir.makepyfile( | ||||||
|  |         _mod=mod_code, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     pkg_mod = pkg / 'mod.py' | ||||||
|  |     pkg_subpkg_submod = subpkg / 'submod.py' | ||||||
|  |     shutil.copyfile( | ||||||
|  |         _submod, | ||||||
|  |         pkg_mod, | ||||||
|  |     ) | ||||||
|  |     shutil.copyfile( | ||||||
|  |         _submod, | ||||||
|  |         pkg_subpkg_submod, | ||||||
|  |     ) | ||||||
|  |     testdir.chdir() | ||||||
|  | 
 | ||||||
|  |     # XXX NOTE, once the "top level" pkg mod has been | ||||||
|  |     # imported, we can then use `import` syntax to | ||||||
|  |     # import it's sub-pkgs and modules. | ||||||
|  |     pkgmod = load_module_from_path( | ||||||
|  |         Path(pkg / '__init__.py'), | ||||||
|  |         module_name=proj_name, | ||||||
|  |     ) | ||||||
|  |     pkg_root_log = tractor.log.get_logger( | ||||||
|  |         _root_name=proj_name, | ||||||
|  |         mk_sublog=False, | ||||||
|  |     ) | ||||||
|  |     assert pkg_root_log.name == proj_name | ||||||
|  |     assert not pkg_root_log.logger.getChildren() | ||||||
|  | 
 | ||||||
|  |     from snakelib import mod | ||||||
|  |     assert mod.log.name == proj_name | ||||||
|  | 
 | ||||||
|  |     from snakelib.subpkg import submod | ||||||
|  |     assert ( | ||||||
|  |         submod.log.name | ||||||
|  |         == | ||||||
|  |         submod.__package__  # ?TODO, use this in `.get_logger()` instead? | ||||||
|  |         == | ||||||
|  |         f'{proj_name}.subpkg' | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     sub_logs = pkg_root_log.logger.getChildren() | ||||||
|  |     assert len(sub_logs) == 1  # only one nested sub-pkg module | ||||||
|  |     assert submod.log.logger in sub_logs | ||||||
|  | 
 | ||||||
|  |     # breakpoint() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| # TODO, moar tests against existing feats: | # TODO, moar tests against existing feats: | ||||||
| # ------ - ------ | # ------ - ------ | ||||||
| # - [ ] color settings? | # - [ ] color settings? | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue