From 0265ee8ef82248a235cd36411d4b468ef7808b43 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:13:03 +0200 Subject: [PATCH 01/10] readme: update version number in links to 1.10.4.1 (Though, it doesn't matter much for the thirdparty packages, as the contents are identical to 1.10.4) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4ca87b174c..62f0d044df 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Installing Panda3D ================== The latest Panda3D SDK can be downloaded from -[this page](https://www.panda3d.org/download/sdk-1-10-4/). +[this page](https://www.panda3d.org/download/sdk-1-10-4-1/). If you are familiar with installing Python packages, you can use the following comand: @@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on building them from source. -https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-win64.zip -https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-win32.zip +https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win64.zip +https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-win32.zip After acquiring these dependencies, you may simply build Panda3D from the command prompt using the following command. (Change `14.1` to `14` if you are @@ -135,7 +135,7 @@ macOS ----- On macOS, you will need to download a set of precompiled thirdparty packages in order to -compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4/panda3d-1.10.4-tools-mac.tar.gz). +compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.4.1/panda3d-1.10.4.1-tools-mac.tar.gz). After placing the thirdparty directory inside the panda3d source directory, you may build Panda3D using a command like the following: From 64982f8b14ca7a165a4f4d0007c52a214c0d92dc Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:21:29 +0200 Subject: [PATCH 02/10] makepanda: makepackage/makewheel take default version from setup.cfg dtool/PandaVersion.pp is obsolete and will be removed soon. --- makepanda/makepackage.py | 2 +- makepanda/makewheel.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index 1094609374..6c9d01cfff 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -1039,7 +1039,7 @@ def MakeInstaller(version, **kwargs): if __name__ == "__main__": - version = ParsePandaVersion("dtool/PandaVersion.pp") + version = GetMetadataValue('version') parser = OptionParser() parser.add_option('', '--version', dest='version', help='Panda3D version number (default: %s)' % (version), default=version) diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index f9b8bb1bec..42f2eca5ef 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -15,7 +15,7 @@ import tempfile import subprocess from distutils.sysconfig import get_config_var from optparse import OptionParser -from makepandacore import ColorText, LocateBinary, ParsePandaVersion, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue +from makepandacore import ColorText, LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue from base64 import urlsafe_b64encode @@ -717,7 +717,7 @@ __version__ = '{0}' if __name__ == "__main__": - version = ParsePandaVersion("dtool/PandaVersion.pp") + version = GetMetadataValue('version') parser = OptionParser() parser.add_option('', '--version', dest = 'version', help = 'Panda3D version number (default: %s)' % (version), default = version) From d5a576f3cb5e18586b5ef9ab6f113390991c7c89 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:14:34 +0200 Subject: [PATCH 03/10] Bump version number on release/1.10.x branch to 1.10.5 --- dtool/PandaVersion.pp | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dtool/PandaVersion.pp b/dtool/PandaVersion.pp index 20de928a91..46200cdd27 100644 --- a/dtool/PandaVersion.pp +++ b/dtool/PandaVersion.pp @@ -7,7 +7,7 @@ // place to put this. // Use spaces to separate the major, minor, and sequence numbers here. -#define PANDA_VERSION 1 10 4 +#define PANDA_VERSION 1 10 5 // This variable will be defined to false in the CVS repository, but // scripts that generate source tarballs and/or binary releases for diff --git a/setup.cfg b/setup.cfg index b63475ad8c..f72764f692 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = Panda3D -version = 1.10.4.1 +version = 1.10.5 url = https://www.panda3d.org/ description = Panda3D is a framework for 3D rendering and game development for Python and C++ programs. license = Modified BSD License From c2f49f4c4a6db1192c5643329ac4b022e2bcd5d4 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:08:36 +0200 Subject: [PATCH 04/10] loader: a few additional checks for Python loader plug-ins --- panda/src/pgraph/pythonLoaderFileType.cxx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/panda/src/pgraph/pythonLoaderFileType.cxx b/panda/src/pgraph/pythonLoaderFileType.cxx index bf563aedb4..3006a13cd3 100644 --- a/panda/src/pgraph/pythonLoaderFileType.cxx +++ b/panda/src/pgraph/pythonLoaderFileType.cxx @@ -83,10 +83,27 @@ init(PyObject *loader) { // it must occur in the list. PyObject *extensions = PyObject_GetAttrString(loader, "extensions"); if (extensions != nullptr) { + if (PyUnicode_Check(extensions) +#if PY_MAJOR_VERSION < 3 + || PyString_Check(extensions) +#endif + ) { + Dtool_Raise_TypeError("extensions list should be a list or tuple"); + Py_DECREF(extensions); + return false; + } + PyObject *sequence = PySequence_Fast(extensions, "extensions must be a sequence"); PyObject **items = PySequence_Fast_ITEMS(sequence); Py_ssize_t num_items = PySequence_Fast_GET_SIZE(sequence); Py_DECREF(extensions); + + if (num_items == 0) { + PyErr_SetString(PyExc_ValueError, "extensions list may not be empty"); + Py_DECREF(sequence); + return false; + } + bool found_extension = false; for (Py_ssize_t i = 0; i < num_items; ++i) { From f63b3a86b9ca808719c0457240026cfc2cbf26d4 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:11:44 +0200 Subject: [PATCH 05/10] loader: add LoaderFileTypeRegistry.unregister_type() --- panda/src/pgraph/loaderFileTypeRegistry.cxx | 35 ++++++++++++++ panda/src/pgraph/loaderFileTypeRegistry.h | 4 ++ .../src/pgraph/loaderFileTypeRegistry_ext.cxx | 48 +++++++++++++++++++ panda/src/pgraph/loaderFileTypeRegistry_ext.h | 2 + panda/src/pgraph/pythonLoaderFileType.h | 3 ++ 5 files changed, 92 insertions(+) diff --git a/panda/src/pgraph/loaderFileTypeRegistry.cxx b/panda/src/pgraph/loaderFileTypeRegistry.cxx index c7df6731eb..2a8d321323 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry.cxx +++ b/panda/src/pgraph/loaderFileTypeRegistry.cxx @@ -113,6 +113,41 @@ register_deferred_type(const string &extension, const string &library) { _deferred_types[dcextension] = library; } +/** + * Removes a type previously registered using register_type. + */ +void LoaderFileTypeRegistry:: +unregister_type(LoaderFileType *type) { + Types::iterator it = find(_types.begin(), _types.end(), type); + if (it == _types.end()) { + if (loader_cat.is_debug()) { + loader_cat.debug() + << "Attempt to unregister LoaderFileType " << type->get_name() + << " (" << type->get_type() << "), which was not registered.\n"; + } + return; + } + + _types.erase(it); + + { + std::string dcextension = downcase(type->get_extension()); + Extensions::iterator ei = _extensions.find(dcextension); + if (ei != _extensions.end() && ei->second == type) { + _extensions.erase(ei); + } + } + + vector_string words; + extract_words(type->get_additional_extensions(), words); + for (const std::string &word : words) { + Extensions::iterator ei = _extensions.find(downcase(word)); + if (ei != _extensions.end() && ei->second == type) { + _extensions.erase(ei); + } + } +} + /** * Returns the total number of types registered. */ diff --git a/panda/src/pgraph/loaderFileTypeRegistry.h b/panda/src/pgraph/loaderFileTypeRegistry.h index 2a4c569329..cccb3af333 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry.h +++ b/panda/src/pgraph/loaderFileTypeRegistry.h @@ -35,10 +35,14 @@ public: void register_type(LoaderFileType *type); void register_deferred_type(const std::string &extension, const std::string &library); + void unregister_type(LoaderFileType *type); + PUBLISHED: EXTENSION(void register_type(PyObject *type)); EXTENSION(void register_deferred_type(PyObject *entry_point)); + EXTENSION(void unregister_type(PyObject *type)); + int get_num_types() const; LoaderFileType *get_type(int n) const; MAKE_SEQ(get_types, get_num_types, get_type); diff --git a/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx b/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx index 0e1df98d02..20450e9195 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx +++ b/panda/src/pgraph/loaderFileTypeRegistry_ext.cxx @@ -17,6 +17,8 @@ #include "pythonLoaderFileType.h" +extern struct Dtool_PyTypedObject Dtool_LoaderFileType; + /** * Registers a loader file type that is implemented in Python. */ @@ -64,4 +66,50 @@ register_deferred_type(PyObject *entry_point) { _this->register_type(loader); } +/** + * If the given loader type is registered, unregisters it. + */ +void Extension:: +unregister_type(PyObject *type) { + // Are we passing in a C++ file type object? + LoaderFileType *extracted_type; + if (DtoolInstance_GetPointer(type, extracted_type, Dtool_LoaderFileType)) { + _this->unregister_type(extracted_type); + return; + } + + // If not, we may be passing in a Python file type. + PyObject *load_func = PyObject_GetAttrString(type, "load_file"); + PyObject *save_func = PyObject_GetAttrString(type, "save_file"); + PyErr_Clear(); + + if (load_func == nullptr && save_func == nullptr) { + Dtool_Raise_TypeError("expected loader type"); + return; + } + + // Keep looping until we've removed all instances of it. + bool found_any; + do { + found_any = false; + size_t num_types = _this->get_num_types(); + for (size_t i = 0; i < num_types; ++i) { + LoaderFileType *type = _this->get_type(i); + if (type->is_of_type(PythonLoaderFileType::get_class_type())) { + PythonLoaderFileType *python_type = (PythonLoaderFileType *)type; + if (python_type->_load_func == load_func && + python_type->_save_func == save_func) { + _this->unregister_type(python_type); + delete python_type; + found_any = true; + break; + } + } + } + } while (found_any); + + Py_XDECREF(load_func); + Py_XDECREF(save_func); +} + #endif diff --git a/panda/src/pgraph/loaderFileTypeRegistry_ext.h b/panda/src/pgraph/loaderFileTypeRegistry_ext.h index 63eee84f72..9c9815c20d 100644 --- a/panda/src/pgraph/loaderFileTypeRegistry_ext.h +++ b/panda/src/pgraph/loaderFileTypeRegistry_ext.h @@ -31,6 +31,8 @@ class Extension : public ExtensionBase; + public: static TypeHandle get_class_type() { return _type_handle; From b23561d863d961fb85183f686ec29f08c4a72fc3 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 13:12:18 +0200 Subject: [PATCH 06/10] tests: add unit tests for Python loader file types --- tests/pgraph/test_loader_types.py | 211 ++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 tests/pgraph/test_loader_types.py diff --git a/tests/pgraph/test_loader_types.py b/tests/pgraph/test_loader_types.py new file mode 100644 index 0000000000..3107a628cc --- /dev/null +++ b/tests/pgraph/test_loader_types.py @@ -0,0 +1,211 @@ +from panda3d.core import LoaderFileTypeRegistry, ModelRoot, Loader, LoaderOptions +import pytest +import tempfile +import os +from contextlib import contextmanager + + +@pytest.fixture +def test_filename(): + """Fixture returning a filename to an existent .test file.""" + fp = tempfile.NamedTemporaryFile(suffix='.test', delete=False) + fp.write(b"test") + fp.close() + yield fp.name + os.unlink(fp.name) + + +@pytest.fixture +def test_pz_filename(): + """Fixture returning a filename to an existent .test.pz file.""" + fp = tempfile.NamedTemporaryFile(suffix='.test.pz', delete=False) + fp.write(b"test") + fp.close() + yield fp.name + os.unlink(fp.name) + + +@contextmanager +def registered_type(type): + """Convenience method allowing use of register_type in a with block.""" + registry = LoaderFileTypeRegistry.get_global_ptr() + registry.register_type(type) + yield + registry.unregister_type(type) + + +class DummyLoader: + """The simplest possible successful LoaderFileType.""" + + extensions = ["test"] + + @staticmethod + def load_file(path, options, record=None): + return ModelRoot("loaded") + + +def test_loader_invalid(): + """Tests that registering a malformed loader fails.""" + + class MissingExtensionsLoader: + pass + + class InvalidTypeExtensionsLoader: + extensions = "abc" + + class EmptyExtensionsLoader: + extensions = [] + + class InvalidExtensionsLoader: + extensions = [123, None] + + registry = LoaderFileTypeRegistry.get_global_ptr() + + with pytest.raises(Exception): + registry.register_type("invalid") + + with pytest.raises(Exception): + registry.register_type(MissingExtensionsLoader) + + with pytest.raises(TypeError): + registry.register_type(InvalidTypeExtensionsLoader) + + with pytest.raises(ValueError): + registry.register_type(EmptyExtensionsLoader) + + with pytest.raises(TypeError): + registry.register_type(InvalidExtensionsLoader) + + +def test_loader_success(test_filename): + """Tests that a normal dummy loader successfully loads.""" + + with registered_type(DummyLoader): + model = Loader.get_global_ptr().load_sync(test_filename, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is not None + assert model.name == "loaded" + + +def test_loader_extensions(test_filename): + """Tests multi-extension loaders.""" + + class MultiExtensionLoader: + extensions = ["test1", "teSt2"] + + @staticmethod + def load_file(path, options, record=None): + return ModelRoot("loaded") + + fp1 = tempfile.NamedTemporaryFile(suffix='.test1', delete=False) + fp1.write(b"test1") + fp1.close() + fp2 = tempfile.NamedTemporaryFile(suffix='.TEST2', delete=False) + fp2.write(b"test2") + fp2.close() + + try: + with registered_type(MultiExtensionLoader): + model1 = Loader.get_global_ptr().load_sync(fp1.name, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model1 is not None + assert model1.name == "loaded" + + model2 = Loader.get_global_ptr().load_sync(fp2.name, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model2 is not None + assert model2.name == "loaded" + finally: + os.unlink(fp1.name) + os.unlink(fp2.name) + + # Ensure that both were unregistered. + registry = LoaderFileTypeRegistry.get_global_ptr() + assert not registry.get_type_from_extension("test1") + assert not registry.get_type_from_extension("test2") + + +def test_loader_nonexistent(): + """Verifies that non-existent files fail before calling load_file.""" + flag = [False] + + class AssertiveLoader: + extensions = ["test"] + + @staticmethod + def load_file(path, options, record=None): + flag[0] = True + assert False, "should never get here" + + with registered_type(AssertiveLoader): + model = Loader.get_global_ptr().load_sync("/non-existent", LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is None + assert not flag[0] + + +def test_loader_exception(test_filename): + """Tests for a loader that raises an exception.""" + + class FailingLoader: + extensions = ["test"] + + @staticmethod + def load_file(path, options, record=None): + raise Exception("test error") + + with registered_type(FailingLoader): + model = Loader.get_global_ptr().load_sync(test_filename, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is None + + +def test_loader_compressed(test_pz_filename): + """Tests for loading .pz files and the supports_compressed flag.""" + + class TestLoader: + extensions = ["test"] + + @staticmethod + def load_file(path, options, record=None): + return ModelRoot("loaded") + + # Test with property absent + with registered_type(TestLoader): + model = Loader.get_global_ptr().load_sync(test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is None + + # Test with property False, should give same result + TestLoader.supports_compressed = False + with registered_type(TestLoader): + model = Loader.get_global_ptr().load_sync(test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is None + + # Test with property True, should work + TestLoader.supports_compressed = True + with registered_type(TestLoader): + model = Loader.get_global_ptr().load_sync(test_pz_filename, LoaderOptions(LoaderOptions.LF_no_cache)) + assert model is not None + assert model.name == "loaded" + + # Test with property invalid type, should not register + TestLoader.supports_compressed = None + with pytest.raises(TypeError): + LoaderFileTypeRegistry.get_global_ptr().register_type(TestLoader) + + +def test_loader_ram_cache(test_filename): + """Tests that the Python loader plug-ins write to the RAM cache.""" + + # Ensure a clean slate. + from panda3d.core import ModelPool + ModelPool.release_all_models() + + with registered_type(DummyLoader): + model1 = Loader.get_global_ptr().load_sync(test_filename, LoaderOptions(LoaderOptions.LF_no_disk_cache | LoaderOptions.LF_allow_instance)) + assert model1 is not None + assert model1.name == "loaded" + + assert ModelPool.has_model(test_filename) + assert ModelPool.get_model(test_filename, True) == model1 + + model2 = Loader.get_global_ptr().load_sync(test_filename, LoaderOptions(LoaderOptions.LF_cache_only | LoaderOptions.LF_allow_instance)) + assert model2 is not None + assert model1 == model2 + + ModelPool.release_model(model2) From 7d34526c33591e91604e2cb62d0c62969f00aed9 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 14:00:15 +0200 Subject: [PATCH 07/10] tests: fix OS-specific filename issue on Windows with loader tests --- tests/pgraph/test_loader_types.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/pgraph/test_loader_types.py b/tests/pgraph/test_loader_types.py index 3107a628cc..aa9f40619a 100644 --- a/tests/pgraph/test_loader_types.py +++ b/tests/pgraph/test_loader_types.py @@ -1,4 +1,4 @@ -from panda3d.core import LoaderFileTypeRegistry, ModelRoot, Loader, LoaderOptions +from panda3d.core import LoaderFileTypeRegistry, ModelRoot, Loader, LoaderOptions, Filename import pytest import tempfile import os @@ -11,7 +11,7 @@ def test_filename(): fp = tempfile.NamedTemporaryFile(suffix='.test', delete=False) fp.write(b"test") fp.close() - yield fp.name + yield Filename.from_os_specific(fp.name) os.unlink(fp.name) @@ -21,7 +21,7 @@ def test_pz_filename(): fp = tempfile.NamedTemporaryFile(suffix='.test.pz', delete=False) fp.write(b"test") fp.close() - yield fp.name + yield Filename.from_os_specific(fp.name) os.unlink(fp.name) @@ -99,17 +99,20 @@ def test_loader_extensions(test_filename): fp1 = tempfile.NamedTemporaryFile(suffix='.test1', delete=False) fp1.write(b"test1") fp1.close() + fn1 = Filename.from_os_specific(fp1.name) + fp2 = tempfile.NamedTemporaryFile(suffix='.TEST2', delete=False) fp2.write(b"test2") fp2.close() + fn2 = Filename.from_os_specific(fp2.name) try: with registered_type(MultiExtensionLoader): - model1 = Loader.get_global_ptr().load_sync(fp1.name, LoaderOptions(LoaderOptions.LF_no_cache)) + model1 = Loader.get_global_ptr().load_sync(fn1, LoaderOptions(LoaderOptions.LF_no_cache)) assert model1 is not None assert model1.name == "loaded" - model2 = Loader.get_global_ptr().load_sync(fp2.name, LoaderOptions(LoaderOptions.LF_no_cache)) + model2 = Loader.get_global_ptr().load_sync(fn2, LoaderOptions(LoaderOptions.LF_no_cache)) assert model2 is not None assert model2.name == "loaded" finally: From f0ba25e11d2bd75a7031518917580964956132d3 Mon Sep 17 00:00:00 2001 From: rdb Date: Tue, 20 Aug 2019 14:40:15 +0200 Subject: [PATCH 08/10] tests: work around Python 2.7 tempfile case bug on Windows --- tests/pgraph/test_loader_types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/pgraph/test_loader_types.py b/tests/pgraph/test_loader_types.py index aa9f40619a..8fca1c5849 100644 --- a/tests/pgraph/test_loader_types.py +++ b/tests/pgraph/test_loader_types.py @@ -11,7 +11,9 @@ def test_filename(): fp = tempfile.NamedTemporaryFile(suffix='.test', delete=False) fp.write(b"test") fp.close() - yield Filename.from_os_specific(fp.name) + filename = Filename.from_os_specific(fp.name) + filename.make_true_case() + yield filename os.unlink(fp.name) @@ -21,7 +23,9 @@ def test_pz_filename(): fp = tempfile.NamedTemporaryFile(suffix='.test.pz', delete=False) fp.write(b"test") fp.close() - yield Filename.from_os_specific(fp.name) + filename = Filename.from_os_specific(fp.name) + filename.make_true_case() + yield filename os.unlink(fp.name) @@ -100,11 +104,13 @@ def test_loader_extensions(test_filename): fp1.write(b"test1") fp1.close() fn1 = Filename.from_os_specific(fp1.name) + fn1.make_true_case() fp2 = tempfile.NamedTemporaryFile(suffix='.TEST2', delete=False) fp2.write(b"test2") fp2.close() fn2 = Filename.from_os_specific(fp2.name) + fn2.make_true_case() try: with registered_type(MultiExtensionLoader): From 2575c01261ab4088ea9134d3bfea18fded37b204 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 22 Aug 2019 10:44:34 +0200 Subject: [PATCH 09/10] Loader: fix passing a tuple to loader.loadModel --- direct/src/showbase/Loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 0ded4d3d30..988982ab28 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -230,7 +230,7 @@ class Loader(DirectObject): """ - assert Loader.notify.debug("Loading model: %s" % (modelPath)) + assert Loader.notify.debug("Loading model: %s" % (modelPath,)) if loaderOptions is None: loaderOptions = LoaderOptions() else: From 88f8071dfce0919da34ec21091d3f11fd8a05505 Mon Sep 17 00:00:00 2001 From: rdb Date: Thu, 22 Aug 2019 10:45:36 +0200 Subject: [PATCH 10/10] tests: add unit tests for direct.showbase.Loader.Loader class --- tests/showbase/test_Loader.py | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/showbase/test_Loader.py diff --git a/tests/showbase/test_Loader.py b/tests/showbase/test_Loader.py new file mode 100644 index 0000000000..0dadecb384 --- /dev/null +++ b/tests/showbase/test_Loader.py @@ -0,0 +1,70 @@ +from panda3d.core import Filename, NodePath +from direct.showbase.Loader import Loader +import pytest + + +@pytest.fixture +def loader(): + return Loader(base=None) + + +@pytest.fixture +def temp_model(): + from panda3d.core import ModelPool, ModelRoot + + root = ModelRoot('model') + root.fullpath = '/test-model.bam' + + ModelPool.add_model(root.fullpath, root) + yield root.fullpath + ModelPool.release_model(root.fullpath) + + +def test_load_model_filename(loader, temp_model): + model = loader.load_model(Filename(temp_model)) + assert model + assert isinstance(model, NodePath) + assert model.name == 'model' + + +def test_load_model_str(loader, temp_model): + model = loader.load_model(str(temp_model)) + assert model + assert isinstance(model, NodePath) + assert model.name == 'model' + + +def test_load_model_list(loader, temp_model): + models = loader.load_model([temp_model, temp_model]) + assert models + assert isinstance(models, list) + assert len(models) == 2 + assert isinstance(models[0], NodePath) + assert isinstance(models[1], NodePath) + + +def test_load_model_tuple(loader, temp_model): + models = loader.load_model((temp_model, temp_model)) + assert models + assert isinstance(models, list) + assert len(models) == 2 + assert isinstance(models[0], NodePath) + assert isinstance(models[1], NodePath) + + +def test_load_model_set(loader, temp_model): + models = loader.load_model({temp_model}) + assert models + assert isinstance(models, list) + assert len(models) == 1 + assert isinstance(models[0], NodePath) + + +def test_load_model_missing(loader): + with pytest.raises(IOError): + loader.load_model('/nonexistent.bam') + + +def test_load_model_okmissing(loader): + model = loader.load_model('/nonexistent.bam', okMissing=True) + assert model is None