Esempio n. 1
0
    def setUp(self):
        """
        Prepare unit test.
        """
        super(TestSetupProjectWizard, self).setUp(
            parameters={"primary_root_name": "primary"}
        )
        self._wizard = sgtk.get_command("setup_project_factory").execute({})

        self._storage_locations = ShotgunPath(
            "Z:\\projects", "/mnt/projects", "/Volumes/projects"
        )
        self._storage_locations.current_os = self.tank_temp

        self.mockgun.update(
            "LocalStorage",
            self.primary_storage["id"],
            self._storage_locations.as_shotgun_dict(),
        )

        # Prepare the wizard for business. All these methods are actually passing
        # information directly to the SetupProjectParams object inside
        # the wizard, so there's no need to test them per-se.
        self._wizard.set_project(self.project["id"], force=True)
        self._wizard.set_use_distributed_mode()

        self.config_uri = os.path.join(self.fixtures_root, "config")
        self._wizard.set_config_uri(self.config_uri)
Esempio n. 2
0
    def test_otls_installed(self):
        """
        Checks that the otls file get installed correctly in Houdini, and that Houdini
        reports them as installed.
        """
        # The alembic app is added and it should have installed two otl files,
        # check that Houdini recognizes this.
        alembic_app = self.engine.apps["tk-houdini-alembicnode"]
        otl_path = self.engine._safe_path_join(alembic_app.disk_location,
                                               "otls")

        # The alembic node should have version folders, so remove root folder from the list,
        # and check that we have one path left which will be the version folder.
        otl_paths = self.engine._get_otl_paths(otl_path)
        otl_paths.remove(otl_path)
        self.assertTrue(len(otl_paths) == 1)

        # Now check both otls were installed in Houdini.
        sanitized_loaded_files = [
            ShotgunPath.from_current_os_path(path)
            for path in hou.hda.loadedFiles()
        ]
        self.assertTrue(
            ShotgunPath.from_current_os_path(
                os.path.join(otl_paths[0], "sgtk_alembic.otl")) in
            sanitized_loaded_files)
        self.assertTrue(
            ShotgunPath.from_current_os_path(
                os.path.join(otl_paths[0], "sgtk_alembic_sop.otl")) in
            sanitized_loaded_files)
    def _browse_path(self, platform):
        """Browse and set the path for the supplied platform.

        :param platform: A string indicating the platform to associate with the
            browsed path. Should match the strings returned by ``sys.platform``.
        """

        # create the dialog
        folder_path = QtGui.QFileDialog.getExistingDirectory(
            parent=self,
            caption="Choose Storage Root Folder",
            options=QtGui.QFileDialog.DontResolveSymlinks |
                    QtGui.QFileDialog.DontUseNativeDialog |
                    QtGui.QFileDialog.ShowDirsOnly
        )

        if not folder_path:
            return

        # create the SG path object. assigning the path to the corresponding
        # OS property below will sanitize
        sg_path = ShotgunPath()

        if platform.startswith("linux"):
            sg_path.linux = folder_path
            self.ui.linux_path_edit.setText(sg_path.linux)
        elif platform == "darwin":
            sg_path.macosx = folder_path
            self.ui.mac_path_edit.setText(sg_path.macosx)
        elif platform == "win32":
            sg_path.windows = folder_path
            self.ui.windows_path_edit.setText(sg_path.windows)
Esempio n. 4
0
    def __check_paths(self, houdini_version, expected_folders):
        """
        Checks that the expected folders are gathered for the correct Houdini version.
        :param houdini_version: The version of Houdini as a tuple of three ints.
        :param expected_folders: The list of paths that we expect want to compare against the engine generated ones.
        :return:
        """
        # Change what the engine thinks the Houdini version is.
        self.engine._houdini_version = houdini_version
        # Ask the engine for the otl paths.
        paths_from_engine = self.engine._get_otl_paths(self.app_otl_folder)
        # We would always expect to get the root otl folder returned.
        expected_folders.insert(0, self.app_otl_folder)

        # Handle forward and backwards slashes so that the comparison doesn't care.
        sanitized_paths_from_engine = [
            ShotgunPath.from_current_os_path(path)
            for path in paths_from_engine
        ]
        sanitized_expected_paths = [
            ShotgunPath.from_current_os_path(path) for path in expected_folders
        ]

        self.assertEqual(
            sanitized_paths_from_engine,
            sanitized_expected_paths,
            "Houdini version number was: v%s.%s.%s" % houdini_version,
        )
    def test_existing_files_not_overwritten(self):
        """
        Ensures that if there were already interpreter files present in the config that they won't be overwritten.
        """
        descriptor = self._write_mock_config()

        interpreter_yml_path = ShotgunPath.get_file_name_from_template(
            os.path.join(descriptor.get_path(), "core", "interpreter_%s.cfg")
        )
        # Do not write sys.executable in this file, otherwise we won't know if we're reading our value
        # or the default value. This means however that we'll have to present the file exists when
        # os.path.exists is called.
        os.makedirs(os.path.dirname(interpreter_yml_path))
        path = os.path.join("a", "b", "c")
        with open(interpreter_yml_path, "w") as fh:
            fh.write(path)

        # We're going to pretend the interpreter location exists
        with patch("os.path.exists", return_value=True):
            # Check that our descriptors sees the value we just wrote to disk
            self.assertEqual(
                descriptor.python_interpreter,
                path
            )
        # Copy the descriptor to its location.
        descriptor.copy(os.path.join(self._cw.path.current_os, "config"))

        # have the interpreter files be written out by the writer. The interpreter file we just
        # wrote should have been left alone.
        self.assertEqual(
            self._write_interpreter_file().current_os, path
        )
    def test_transactions(self):
        """
        Ensures the transaction flags are properly handled for a config.
        """
        new_config_root = os.path.join(self.tank_temp, self.short_test_name)

        writer = ConfigurationWriter(
            ShotgunPath.from_current_os_path(new_config_root),
            self.mockgun
        )

        # Test standard transaction flow.
        # Non pending -> Pending -> Non pending
        self.assertEqual(False, writer.is_transaction_pending())
        writer.start_transaction()
        self.assertEqual(True, writer.is_transaction_pending())
        writer.end_transaction()
        self.assertEqual(False, writer.is_transaction_pending())

        # Remove the transaction folder
        writer._delete_state_file(writer._TRANSACTION_START_FILE)
        writer._delete_state_file(writer._TRANSACTION_END_FILE)
        # Even if the marker is missing, the API should report no pending transactions since the
        # transaction folder doesn't even exist, which will happen for configurations written
        # with a previous version of core.
        self.assertEqual(False, writer.is_transaction_pending())

        # We've deleted both the transaction files and now we're writing the end transaction file.
        # If we're in that state, we'll assume something is broken and say its pending since the
        # config was tinkered with.
        writer.end_transaction()
        self.assertEqual(True, writer.is_transaction_pending())
Esempio n. 7
0
    def test_installed_configuration_not_on_disk(self):
        """
        Ensure that the resolver detects when an installed configuration has not been set for the
        current platform.
        """
        # Create a pipeline configuration.
        pc_id = self._create_pc(
            "Primary",
            self._project,
            "sg_path",
            plugin_ids="foo.*",
        )["id"]

        # Remove the current platform's path.
        self.mockgun.update(
            "PipelineConfiguration",
            pc_id,
            {
                ShotgunPath.get_shotgun_storage_key(): None
            }
        )

        with self.assertRaisesRegex(
            sgtk.bootstrap.TankBootstrapError,
            "The Shotgun pipeline configuration with id %s has no source location specified for "
            "your operating system." % pc_id
        ):
            self.resolver.resolve_shotgun_configuration(
                pipeline_config_identifier=pc_id,
                fallback_config_descriptor=self.config_1,
                sg_connection=self.mockgun,
                current_login="******"
            )
    def test_load_snapshot(self):
        """
        Tests loading a snapshot, there is no API method for this, so we are calling internal app functions.
        """
        # Create a file for the test to load.
        file_path = self._create_file("banana")
        # Reset the scene so it won't prompt the test to save.
        self._reset_scene()

        handler = self.app.tk_multi_snapshot.Snapshot(self.app)
        handler._do_scene_operation("open", file_path)
        # Now check that the file Houdini has open is the same as the one we originally saved.
        self.assertEqual(
            ShotgunPath.from_current_os_path(file_path),
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
        )
Esempio n. 9
0
 def setUp(self):
     # Makes sure every unit test run in its own sandbox.
     super(TestInterpreterFilesWriter, self).setUp()
     self._root = os.path.join(self.tank_temp, self.short_test_name)
     os.makedirs(self._root)
     self._cw = ConfigurationWriter(
         ShotgunPath.from_current_os_path(self._root), self.mockgun)
Esempio n. 10
0
    def test_installed_configuration_not_on_disk(self):
        """
        Ensure that the resolver detects when an installed configuration has not been set for the
        current platform.
        """
        # Create a pipeline configuration.
        pc_id = self._create_pc(
            "Primary",
            self._project,
            "sg_path",
            plugin_ids="foo.*",
        )["id"]

        # Remove the current platform's path.
        self.mockgun.update("PipelineConfiguration", pc_id,
                            {ShotgunPath.get_shotgun_storage_key(): None})

        with self.assertRaisesRegexp(
                sgtk.bootstrap.TankBootstrapError,
                "The Shotgun pipeline configuration with id %s has no source location specified for "
                "your operating system." % pc_id):
            self.resolver.resolve_shotgun_configuration(
                pipeline_config_identifier=pc_id,
                fallback_config_descriptor=self.config_1,
                sg_connection=self.mockgun,
                current_login="******")
Esempio n. 11
0
    def test_configuration_not_found_on_disk(self):
        """
        Ensure that the resolver detects when an installed configuration is not available for the
        current platform.
        """
        this_path_does_not_exists = "/this/does/not/exists/on/disk"
        pc_id = self._create_pc(
            "Primary",
            None,
            this_path_does_not_exists
        )["id"]

        expected_descriptor_dict = ShotgunPath(
            this_path_does_not_exists, this_path_does_not_exists, this_path_does_not_exists
        ).as_shotgun_dict()
        expected_descriptor_dict["type"] = "path"

        with self.assertRaisesRegexp(
            sgtk.bootstrap.TankBootstrapError,
            "Installed pipeline configuration '.*' does not exist on disk!"
        ):
            self.resolver.resolve_shotgun_configuration(
                pc_id,
                [],
                self.mockgun,
                "john.smith"
            )
Esempio n. 12
0
    def test_existing_files_not_overwritten(self):
        """
        Ensures that if there were already interpreter files present in the config that they won't be overwritten.
        """
        descriptor = self._write_mock_config()

        interpreter_yml_path = ShotgunPath.get_file_name_from_template(
            os.path.join(descriptor.get_path(), "core", "interpreter_%s.cfg"))
        # Do not write sys.executable in this file, otherwise we won't know if we're reading our value
        # or the default value. This means however that we'll have to present the file exists when
        # os.path.exists is called.
        os.makedirs(os.path.dirname(interpreter_yml_path))
        path = os.path.join("a", "b", "c")
        with open(interpreter_yml_path, "w") as fh:
            fh.write(path)

        # We're going to pretend the interpreter location exists
        with patch("os.path.exists", return_value=True):
            # Check that our descriptors sees the value we just wrote to disk
            self.assertEqual(descriptor.python_interpreter, path)
        # Copy the descriptor to its location.
        descriptor.copy(os.path.join(self._cw.path.current_os, "config"))

        # have the interpreter files be written out by the writer. The interpreter file we just
        # wrote should have been left alone.
        self.assertEqual(self._write_interpreter_file().current_os, path)
    def _create_test_data(self, create_project):
        """
        Creates test data, including
            - __pipeline_configuration, a shotgun entity dict.
            - optional __project entity dict, linked from the pipeline configuration
            - __descriptor, a sgtk.descriptor.Descriptor refering to a config on disk.
            - __cw, a ConfigurationWriter
        """

        if create_project:
            self.__project = self.mockgun.create("Project", {
                "code": "TestWritePipelineConfigFile",
                "tank_name": "pc_tank_name"
            })
        else:
            self.__project = None

        self.__pipeline_configuration = self.mockgun.create(
            "PipelineConfiguration", {
                "code": "PC_TestWritePipelineConfigFile",
                "project": self.__project
            })

        self.__descriptor = sgtk.descriptor.create_descriptor(
            self.mockgun, sgtk.descriptor.Descriptor.CONFIG,
            dict(type="dev", path="/a/b/c"))

        config_root = os.path.join(self.tank_temp, self.id())

        self.__cw = ConfigurationWriter(
            ShotgunPath.from_current_os_path(config_root), self.mockgun)
        os.makedirs(os.path.join(config_root, "config", "core"))
Esempio n. 14
0
    def _write_interpreter_file(self,
                                executable=sys.executable,
                                prefix=sys.prefix):
        """
        Writes the interpreter file to disk based on an executable and prefix.

        :returns: Path that was written in each interpreter file.
        :rtype: sgtk.util.ShotgunPath
        """
        core_folder = os.path.join(self._root, "config", "core")
        if not os.path.exists(core_folder):
            os.makedirs(core_folder)
        os.makedirs(
            os.path.join(self._root, "install", "core", "setup",
                         "root_binaries"))

        self._cw.create_tank_command(executable, prefix)

        interpreters = []
        for platform in ["Windows", "Linux", "Darwin"]:
            file_name = os.path.join(self._root, "config", "core",
                                     "interpreter_%s.cfg" % platform)

            with open(file_name, "r") as w:
                interpreters.append(w.read())

        return ShotgunPath(*interpreters)
Esempio n. 15
0
    def test_transactions(self):
        """
        Ensures the transaction flags are properly handled for a config.
        """
        new_config_root = os.path.join(self.tank_temp, self.short_test_name)

        writer = ConfigurationWriter(
            ShotgunPath.from_current_os_path(new_config_root), self.mockgun)

        # Test standard transaction flow.
        # Non pending -> Pending -> Non pending
        self.assertEqual(False, writer.is_transaction_pending())
        writer.start_transaction()
        self.assertEqual(True, writer.is_transaction_pending())
        writer.end_transaction()
        self.assertEqual(False, writer.is_transaction_pending())

        # Remove the transaction folder
        writer._delete_state_file(writer._TRANSACTION_START_FILE)
        writer._delete_state_file(writer._TRANSACTION_END_FILE)
        # Even if the marker is missing, the API should report no pending transactions since the
        # transaction folder doesn't even exist, which will happen for configurations written
        # with a previous version of core.
        self.assertEqual(False, writer.is_transaction_pending())

        # We've deleted both the transaction files and now we're writing the end transaction file.
        # If we're in that state, we'll assume something is broken and say its pending since the
        # config was tinkered with.
        writer.end_transaction()
        self.assertEqual(True, writer.is_transaction_pending())
Esempio n. 16
0
 def _get_default_intepreters(self):
     """
     Gets the default interpreter values for the Shotgun Desktop.
     """
     return ShotgunPath(
         r"C:\Program Files\Shotgun\Python\python.exe",
         "/opt/Shotgun/Python/bin/python",
         "/Applications/Shotgun.app/Contents/Resources/Python/bin/python")
Esempio n. 17
0
    def test_get_current_path(self):
        """
        Tests the scene operation hooks current_path operation.
        """

        # Create a temporary scene file, so we can test that we can get the current path to it.
        created_file = self._create_file("temp")
        # Make sure the scene file we created matches what Houdini believes to be the scene file.
        self.assertEqual(hou.hipFile.name(), created_file)

        result = self.scene_operation.get_current_path(
            self.app, self.scene_operation.NEW_FILE_ACTION, self.engine.context
        )
        self.assertEqual(
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
            ShotgunPath.from_current_os_path(result),
        )
 def setUp(self):
     # Makes sure every unit test run in its own sandbox.
     super(TestInterpreterFilesWriter, self).setUp()
     self._root = os.path.join(self.tank_temp, self.short_test_name)
     os.makedirs(self._root)
     self._cw = ConfigurationWriter(
         ShotgunPath.from_current_os_path(self._root),
         self.mockgun
     )
    def _create_test_data(self, create_project):
        """
        Creates test data, including
            - __site_configuration, a shotgun entity dict.
            - optional __project entity dict, linked from the pipeline configuration
            - __descriptor, a sgtk.descriptor.Descriptor refering to a config on disk.
            - __cw, a ConfigurationWriter
        """

        if create_project:
            self.__project = self.mockgun.create(
                "Project",
                {
                    "code": "TestWritePipelineConfigFile",
                    "tank_name": "pc_tank_name"
                }
            )
        else:
            self.__project = None

        self.__site_configuration = self.mockgun.create(
            "PipelineConfiguration",
            {
                "code": "PC_TestWritePipelineConfigFile",
                "project": None
            }
        )

        self.__project_configuration = self.mockgun.create(
            "PipelineConfiguration",
            {
                "code": "PC_TestWritePipelineConfigFile",
                "project": self.__project
            }
        )

        self.__descriptor = sgtk.descriptor.create_descriptor(
            self.mockgun,
            sgtk.descriptor.Descriptor.CONFIG,
            dict(type="dev", path="/a/b/c")
        )

        config_root = os.path.join(self.tank_temp, self.short_test_name)

        self.__cw = ConfigurationWriter(
            ShotgunPath.from_current_os_path(config_root),
            self.mockgun
        )
        os.makedirs(
            os.path.join(
                config_root,
                "config",
                "core"
            )
        )
Esempio n. 20
0
 def _open_browser(self):
     """
     Method called to open the folder browser.
     """
     self.ui.vhd_location_le.clear()
     dir = QtGui.QFileDialog.getExistingDirectory(parent=self)
     if dir:
         norm_path = ShotgunPath.normalize(dir)
         if not norm_path.endswith('\\'):
             norm_path = '{0}\\'.format(norm_path)
         self.ui.vhd_location_le.insert(norm_path)
Esempio n. 21
0
    def test_reset(self):
        """
        Tests the scene operation hooks reset operation.
        """
        # Create a temporary scene file, so we can test the reset works.
        created_file = self._create_file("temp")
        # Make sure the scene file we created matches what Houdini believes to be the scene file.
        self.assertEqual(
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
            ShotgunPath.from_current_os_path(created_file),
        )

        result = self.scene_operation.reset_current_scene(
            self.app, self.scene_operation.NEW_FILE_ACTION, self.engine.context
        )
        self.assertTrue(result)
        # When we reset the file name should be untitled.hip
        self.assertEqual(
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
            ShotgunPath.from_current_os_path("untitled.hip"),
        )
    def setUp(self):
        super(TestTankFromPathDuplicatePcPaths, self).setUp()

        # define an additional pipeline config with overlapping paths
        self.overlapping_pc = {
            "type": "PipelineConfiguration",
            "code": "Primary",
            "id": 123456,
            "project": self.project,
            ShotgunPath.get_shotgun_storage_key(): self.project_root,
        }

        self.add_to_sg_mock_db(self.overlapping_pc)
    def setUp(self):
        super(TestTankFromPathDuplicatePcPaths, self).setUp()

        # define an additional pipeline config with overlapping paths
        self.overlapping_pc = {
            "type": "PipelineConfiguration",
            "code": "Primary",
            "id": 123456,
            "project": self.project,
            ShotgunPath.get_shotgun_storage_key(): self.project_root,
        }

        self.add_to_sg_mock_db(self.overlapping_pc)
Esempio n. 24
0
    def test_open_file(self):
        """
        Tests the scene operation hooks open operation.
        """

        created_file = self._create_file("dog")

        # Reset the scene so it is empty in preparation for opening the file we just saved.
        self._reset_scene()

        self.scene_operation.open_file(
            self.app,
            self.scene_operation.NEW_FILE_ACTION,
            self.engine.context,
            created_file,
            1,
            False,
        )

        self.assertEqual(
            ShotgunPath.from_current_os_path(created_file),
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
        )
Esempio n. 25
0
    def test_save_file(self):
        """
        Tests the scene operation hooks save operation.
        """

        save_path = self._get_new_file_path("work_path", "cat")

        # test saving a new file.
        self.scene_operation.save_file(
            self.app,
            self.scene_operation.NEW_FILE_ACTION,
            self.engine.context,
            path=save_path,
        )
        self.assertEqual(
            ShotgunPath.from_current_os_path(save_path),
            ShotgunPath.from_current_os_path(hou.hipFile.name()),
        )

        # Now test saving over the same file.
        self.scene_operation.save_file(
            self.app, self.scene_operation.NEW_FILE_ACTION, self.engine.context
        )
Esempio n. 26
0
    def _create_configuration_writer(self):
        """
        Creates a configuration writer that will write to a unique folder for this test.
        """
        new_config_root = os.path.join(self.tank_temp, "new_configuration",
                                       self.short_test_name)
        shotgun_yml_root = os.path.join(new_config_root, "config", "core")
        # Ensures the location for the shotgun.yml exists.
        os.makedirs(shotgun_yml_root)

        writer = ConfigurationWriter(
            ShotgunPath.from_current_os_path(new_config_root), self.mockgun)
        writer.ensure_project_scaffold()
        return writer
    def test_character_escaping(self):
        """
        Ensure that the ' characte is properly escaped
        when writing out install_location.yml
        """
        new_config_root = os.path.join(self.tank_temp, self.short_test_name,
                                       "O'Connell")

        writer = ConfigurationWriter(
            ShotgunPath.from_current_os_path(new_config_root), self.mockgun)

        install_location_path = os.path.join(new_config_root, "config", "core",
                                             "install_location.yml")

        os.makedirs(os.path.dirname(install_location_path))

        writer.write_install_location_file()

        with open(install_location_path, "rt") as f:
            paths = yaml.safe_load(f)
            path = ShotgunPath(paths["Windows"], paths["Linux"],
                               paths["Darwin"])
        assert path.current_os == new_config_root
Esempio n. 28
0
    def test_execute(self):
        """
        Ensure we can set up the project.
        """
        self._wizard.set_project_disk_name(self.short_test_name)
        path = ShotgunPath.from_current_os_path(
            os.path.join(self.tank_temp, self.short_test_name, "pipeline")
        )
        self._wizard.set_configuration_location(path.linux, path.windows, path.macosx)

        # Upload method not implemented on Mockgun yet, so skip that bit.
        with patch("tank_vendor.shotgun_api3.lib.mockgun.mockgun.Shotgun.upload"):
            with patch("tank.pipelineconfig_utils.get_core_api_version") as api_mock:
                api_mock.return_value = "HEAD"
                self._wizard.execute()
    def _create_configuration_writer(self):
        """
        Creates a configuration writer that will write to a unique folder for this test.
        """
        new_config_root = os.path.join(self.tank_temp, "new_configuration", self.short_test_name)
        shotgun_yml_root = os.path.join(new_config_root, "config", "core")
        # Ensures the location for the shotgun.yml exists.
        os.makedirs(shotgun_yml_root)

        writer = ConfigurationWriter(
            ShotgunPath.from_current_os_path(new_config_root),
            self.mockgun
        )
        writer.ensure_project_scaffold()
        return writer
Esempio n. 30
0
 def test_get_core_settings(self):
     """
     Ensure we can find the core settings. Given this is a unit test and not
     running off a real core, there's nothing more we can do at the moment.
     """
     # Core is installed as
     # <studio-install>/install/core/python
     # This file is under the equivalent of
     # <studio-install>/install/core/tests/commands_tests/test_project_wizard.py
     # So we have to pop 4 folders to get back the equivalent location.
     install_location = os.path.normpath(
         os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")
     )
     self.assertEqual(
         self._wizard.get_core_settings(),
         {
             "core_path": ShotgunPath.from_current_os_path(
                 install_location
             ).as_system_dict(),
             "localize": True,
             "pipeline_config": None,
             "using_runtime": True,
         },
     )
Esempio n. 31
0
    def validatePage(self):
        """The 'next' button was pushed. See if the mappings are valid."""

        logger.debug("Validating the storage mappings page...")

        # the wizard instance and its UI
        wiz = self.wizard()
        ui = wiz.ui

        # clear any errors
        ui.storage_errors.setText("")

        # get the path key for the current os
        current_os_key = ShotgunPath.get_shotgun_storage_key()

        logger.debug("Current OS storage path key: %s" % (current_os_key, ))

        # temp lists of widgets that need attention
        invalid_widgets = []
        not_on_disk_widgets = []

        # keep track of the first invalid widget so we can ensure it is visible
        # to the user in the list.
        first_invalid_widget = None

        logger.debug("Checking all map widgets...")

        # see if each of the mappings is valid
        for map_widget in self._map_widgets:

            logger.debug("Checking mapping for root: %s" %
                         (map_widget.root_name, ))

            if not map_widget.mapping_is_valid():
                # something is wrong with this widget's mapping
                invalid_widgets.append(map_widget)
                if first_invalid_widget is None:
                    first_invalid_widget = map_widget

            storage = map_widget.local_storage or {}
            current_os_path = storage.get(current_os_key)

            if current_os_path and not os.path.exists(current_os_path):
                # the current os path for this widget doesn't exist on disk
                not_on_disk_widgets.append(map_widget)

        if invalid_widgets:
            # tell the user which roots don't have valid mappings
            root_names = [w.root_name for w in invalid_widgets]
            logger.debug("Invalid mappings for roots: %s" % (root_names))
            ui.storage_errors.setText(
                "The mappings for these roots are invalid: <b>%s</b>" %
                (", ".join(root_names), ))
            if first_invalid_widget:
                ui.storage_map_area.ensureWidgetVisible(first_invalid_widget)
            return False

        if not_on_disk_widgets:

            # try to create the folders for current OS if they don't exist
            failed_to_create = []
            for widget in not_on_disk_widgets:

                storage = widget.local_storage
                folder = storage[current_os_key]

                logger.debug("Ensuring folder on disk for storage '%s': %s" %
                             (storage["code"], folder))

                # try to create the missing path for the current OS. this will
                # help ensure the storage specified in SG is valid and the
                # project data can be written to this root.
                try:
                    ensure_folder_exists(folder)
                except Exception:
                    logger.error("Failed to create folder: %s" % (folder, ))
                    logger.error(traceback.format_exc())
                    failed_to_create.append(storage["code"])

            if failed_to_create:
                # some folders weren't created. let the user know.
                ui.storage_errors.setText(
                    "Unable to create folders on disk for these storages: %s."
                    "Please check to make sure you have permission to create "
                    "these folders. See the tk-desktop log for more info." %
                    (", ".join(failed_to_create), ))

        # ---- now we've mapped the roots, and they're all valid, we need to
        #      update the root information on the core wizard

        for map_widget in self._map_widgets:

            root_name = map_widget.root_name
            root_info = map_widget.root_info
            storage_data = map_widget.local_storage

            # populate the data defined prior to mapping
            updated_storage_data = root_info

            # update the mapped shotgun data
            updated_storage_data["shotgun_storage_id"] = storage_data["id"]
            updated_storage_data["linux_path"] = str(
                storage_data["linux_path"])
            updated_storage_data["mac_path"] = str(storage_data["mac_path"])
            updated_storage_data["windows_path"] = str(
                storage_data["windows_path"])

            # now update the core wizard's root info
            wiz.core_wizard.update_storage_root(self._uri, root_name,
                                                updated_storage_data)

            # store the fact that we've mapped this root name with this
            # storage name. we can use this information to make better
            # guesses next time this user is mapping storages.
            self._historical_mappings[root_name] = storage_data["code"]
            self._settings.store(self.HISTORICAL_MAPPING_KEY,
                                 self._historical_mappings)

        logger.debug("Storage mappings are valid.")

        # if we made it here, then we should be valid.
        try:
            wiz.core_wizard.set_config_uri(self._uri)
        except Exception as e:
            error = ("Unknown error when setting the configuration uri:\n%s" %
                     str(e))
            logger.error(error)
            logger.error(traceback.print_exc())
            ui.storage_errors.setText(error)
            return False

        return True
Esempio n. 32
0
CREATE_DEFAULT_LOCATION = ShotgunPath.from_shotgun_dict({
    "windows_path":
    os.path.abspath(
        os.path.join(
            os.sep,
            # The name of the folder can change based on the locale.
            # https://www.samlogic.net/articles/program-files-folder-different-languages.htm
            # The safe way to retrieve the path to the Program Files folder is to read the env var
            # Warning: Running this code on non-window trigger a KeyError
            # This is why we use get with a dummy default value.
            os.environ.get("ProgramFiles", "%ProgramFiles%"),
            "Autodesk",
            "Shotgun Create",
            "bin",
            "ShotgunCreate.exe",
        )),
    "mac_path":
    os.path.abspath(
        os.path.join(
            os.sep,
            "Applications",
            "Autodesk",
            "Shotgun Create.app",
            "Contents",
            "MacOS",
            "Shotgun Create",
        )),
    "linux_path":
    os.path.abspath(
        os.path.join(os.sep, "opt", "Autodesk", "ShotgunCreate", "bin",
                     "ShotgunCreate")),
})
    def _on_path_changed(self, path, platform):
        """
        Keep track of any path edits as they happen. Keep the user informed if
        there are any concerns about the entered text.

        :param path: The path that has changed.
        :param platform: The platform the modified path is associated with.
        """

        # does the path only contain slashes?
        only_slashes = path.replace("/", "").replace("\\", "") == ""

        # does it end in a slash?
        trailing_slash = path.endswith("/") or path.endswith("\\")

        # the name of the storage being edited
        storage_name = str(self.ui.storage_select_combo.currentText())

        # a temp SG path object used for sanitization
        sg_path = ShotgunPath()

        # store the edited path in the appropriate path lookup. sanitize first
        # by running it through the ShotgunPath object. since sanitize removes
        # the trailing slash, add it back in if the user typed it.
        # if the sanitized path differs, update the edit.
        if platform.startswith("linux"):
            if only_slashes:
                # SG path code doesn't like only slashes in a path
                self._linux_path_edit[storage_name] = path
            elif path:
                sg_path.linux = path  # sanitize
                sanitized_path = sg_path.linux
                if trailing_slash:
                    # add the trailing slash back in
                    sanitized_path = "%s/" % (sanitized_path,)
                if sanitized_path != path:
                    # path changed due to sanitation. change it in the UI
                    self.ui.linux_path_edit.setText(sanitized_path)
                # remember the sanitized path
                self._linux_path_edit[storage_name] = sanitized_path
            else:
                # no path. update the edit lookup to reflect
                self._linux_path_edit[storage_name] = ""
        elif platform == "darwin":
            if only_slashes:
                # SG path code doesn't like only slashes in a path
                self._mac_path_edit[storage_name] = path
            elif path:
                sg_path.macosx = path  # sanitize
                sanitized_path = sg_path.macosx
                if trailing_slash:
                    # add the trailing slash back in
                    sanitized_path = "%s/" % (sanitized_path,)
                if sanitized_path != path:
                    # path changed due to sanitation. change it in the UI
                    self.ui.mac_path_edit.setText(sanitized_path)
                # remember the sanitized path
                self._mac_path_edit[storage_name] = sanitized_path
            else:
                # no path. update the edit lookup to reflect
                self._mac_path_edit[storage_name] = ""
        elif platform == "win32":
            if only_slashes:
                # SG path code doesn't like only slashes in a path
                self._windows_path_edit[storage_name] = path
            elif path:
                sg_path.windows = path  # sanitize
                sanitized_path = sg_path.windows
                if trailing_slash and not sanitized_path.endswith("\\"):
                    # add the trailing slash back in
                    sanitized_path = "%s\\" % (sanitized_path,)
                if sanitized_path != path:
                    # path changed due to sanitation. change it in the UI
                    self.ui.windows_path_edit.setText(sanitized_path)
                # remember the sanitized path
                self._windows_path_edit[storage_name] = sanitized_path
            else:
                # no path. update the edit lookup to reflect
                self._windows_path_edit[storage_name] = ""

        # run the validation tell the user if there are issues
        self.mapping_is_valid()
    def validatePage(self):
        """The 'next' button was pushed. See if the mappings are valid."""

        logger.debug("Validating the storage mappings page...")

        # the wizard instance and its UI
        wiz = self.wizard()
        ui = wiz.ui

        # clear any errors
        ui.storage_errors.setText("")

        # get the path key for the current os
        current_os_key = ShotgunPath.get_shotgun_storage_key()

        logger.debug("Current OS storage path key: %s" % (current_os_key,))

        # temp lists of widgets that need attention
        invalid_widgets = []
        not_on_disk_widgets = []

        # keep track of the first invalid widget so we can ensure it is visible
        # to the user in the list.
        first_invalid_widget = None

        logger.debug("Checking all map widgets...")

        # see if each of the mappings is valid
        for map_widget in self._map_widgets:

            logger.debug(
                "Checking mapping for root: %s" %
                (map_widget.root_name,)
            )

            if not map_widget.mapping_is_valid():
                # something is wrong with this widget's mapping
                invalid_widgets.append(map_widget)
                if first_invalid_widget is None:
                    first_invalid_widget = map_widget

            storage = map_widget.local_storage or {}
            current_os_path = storage.get(current_os_key)

            if current_os_path and not os.path.exists(current_os_path):
                # the current os path for this widget doesn't exist on disk
                not_on_disk_widgets.append(map_widget)

        if invalid_widgets:
            # tell the user which roots don't have valid mappings
            root_names = [w.root_name for w in invalid_widgets]
            logger.debug("Invalid mappings for roots: %s" % (root_names))
            ui.storage_errors.setText(
                "The mappings for these roots are invalid: <b>%s</b>" %
                (", ".join(root_names),)
            )
            if first_invalid_widget:
                ui.storage_map_area.ensureWidgetVisible(first_invalid_widget)
            return False

        if not_on_disk_widgets:

            # try to create the folders for current OS if they don't exist
            failed_to_create = []
            for widget in not_on_disk_widgets:

                storage = widget.local_storage
                folder = storage[current_os_key]

                logger.debug(
                    "Ensuring folder on disk for storage '%s': %s" %
                    (storage["code"], folder)
                )

                # try to create the missing path for the current OS. this will
                # help ensure the storage specified in SG is valid and the
                # project data can be written to this root.
                try:
                    ensure_folder_exists(folder)
                except Exception:
                    logger.error("Failed to create folder: %s" % (folder,))
                    logger.error(traceback.format_exc())
                    failed_to_create.append(storage["code"])

            if failed_to_create:
                # some folders weren't created. let the user know.
                ui.storage_errors.setText(
                    "Unable to create folders on disk for these storages: %s."
                    "Please check to make sure you have permission to create "
                    "these folders. See the tk-desktop log for more info." %
                    (", ".join(failed_to_create),)
                )

        # ---- now we've mapped the roots, and they're all valid, we need to
        #      update the root information on the core wizard

        for map_widget in self._map_widgets:

            root_name = map_widget.root_name
            root_info = map_widget.root_info
            storage_data = map_widget.local_storage

            # populate the data defined prior to mapping
            updated_storage_data = root_info

            # update the mapped shotgun data
            updated_storage_data["shotgun_storage_id"] = storage_data["id"]
            updated_storage_data["linux_path"] = str(storage_data["linux_path"])
            updated_storage_data["mac_path"] = str(storage_data["mac_path"])
            updated_storage_data["windows_path"] = str(
                storage_data["windows_path"])

            # now update the core wizard's root info
            wiz.core_wizard.update_storage_root(
                self._uri,
                root_name,
                updated_storage_data
            )

            # store the fact that we've mapped this root name with this
            # storage name. we can use this information to make better
            # guesses next time this user is mapping storages.
            self._historical_mappings[root_name] = storage_data["code"]
            self._settings.store(
                self.HISTORICAL_MAPPING_KEY,
                self._historical_mappings
            )

        logger.debug("Storage mappings are valid.")

        # if we made it here, then we should be valid.
        try:
            wiz.core_wizard.set_config_uri(self._uri)
        except Exception as e:
            error = (
                "Unknown error when setting the configuration uri:\n%s" %
                str(e)
            )
            logger.error(error)
            logger.error(traceback.print_exc())
            ui.storage_errors.setText(error)
            return False

        return True
Esempio n. 35
0
class TestSetupProjectWizard(TankTestBase):
    """
    Makes sure environment code works with the app store mocker.
    """

    def setUp(self):
        """
        Prepare unit test.
        """
        super(TestSetupProjectWizard, self).setUp(
            parameters={"primary_root_name": "primary"}
        )
        self._wizard = sgtk.get_command("setup_project_factory").execute({})

        self._storage_locations = ShotgunPath(
            "Z:\\projects", "/mnt/projects", "/Volumes/projects"
        )
        self._storage_locations.current_os = self.tank_temp

        self.mockgun.update(
            "LocalStorage",
            self.primary_storage["id"],
            self._storage_locations.as_shotgun_dict(),
        )

        # Prepare the wizard for business. All these methods are actually passing
        # information directly to the SetupProjectParams object inside
        # the wizard, so there's no need to test them per-se.
        self._wizard.set_project(self.project["id"], force=True)
        self._wizard.set_use_distributed_mode()

        self.config_uri = os.path.join(self.fixtures_root, "config")
        self._wizard.set_config_uri(self.config_uri)

    def test_validate_config_uri(self):
        """
        Ensure that we can validate the URI.

        This doesn't actually test much, it is simply there as a proof that
        there is a bug in the API right now. We should get back to this in the
        future.
        """
        storage_setup = self._wizard.validate_config_uri(self.config_uri)

        expected_primary_storage = {
            "default": True,
            "defined_in_shotgun": True,
            "description": "Default location where project data is stored.",
            "exists_on_disk": True,
            "shotgun_id": self.primary_storage["id"],
            # FIXME: This is a bug. The StorageRoots instance, owned by the SetupProjectParams,
            # is initialized to these values by default. They are then injected into
            # the result of validate_config_uri. validate_config_uri is expected
            # however to return paths named after sys.platform and not <os>_path.
            # We can review this once the Python 3 port is done.
            "linux_path": "/studio/projects",
            "mac_path": "/studio/projects",
            "windows_path": "\\\\network\\projects",
        }
        # Inject the storage locations we set on the local storage earlier.
        expected_primary_storage.update(self._storage_locations.as_system_dict())
        self.assertEqual(storage_setup, {"primary": expected_primary_storage})

    def test_set_project_disk_name(self):
        """
        Ensure the project folder gets created or not on demand.
        """
        # Make sure the config we have is valid.
        project_locations = self._storage_locations.join(self.short_test_name)

        self._wizard.set_project_disk_name(self.short_test_name, False)
        self.assertFalse(os.path.exists(project_locations.current_os))
        self._wizard.set_project_disk_name(self.short_test_name, True)
        self.assertTrue(os.path.exists(project_locations.current_os))

    def test_preview_project_paths(self):
        """
        Ensure all project paths get returned properly.
        """
        self.assertEqual(
            self._wizard.preview_project_paths(self.short_test_name),
            {
                "primary": self._storage_locations.join(
                    self.short_test_name
                ).as_system_dict()
            },
        )

    def test_default_configuration_location_without_suggestions(self):
        """
        Ensure that when no matching pipeline configurations are found that
        we do not get a suggestion back.
        """
        self._wizard.set_project_disk_name(self.short_test_name)
        locations = self._wizard.get_default_configuration_location()
        self.assertEqual(locations, {"win32": None, "darwin": None, "linux2": None})

    def test_default_configuration_location_with_existing_pipeline_configuration(self):
        """
        Ensure that when the tank_name and the configuration folder name are the same
        for the latest configuration found in Shotgun, we'll offer a pre-baked path
        to the user using the new project name.

        For e.g., if a project with a tank_name set to "potato" and whose configuration
        was written to "/vegatables/potato", then a new project with tank name "radish"
        would get a default location of "/vegatables/radish".
        """
        self._wizard.set_project_disk_name(self.short_test_name)

        # Create a project with a tank name matching the name of the folder for the pipeline configuration
        other_project = self.mockgun.create(
            "Project", {"name": "Other Project", "tank_name": "other_project"}
        )
        self.mockgun.create(
            "PipelineConfiguration",
            {
                "code": "primary",
                "created_at": datetime.datetime.now(),
                "project": other_project,
                "mac_path": "/Volumes/configs/other_project",
                "linux_path": "/mnt/configs/other_project",
                "windows_path": "Z:\\configs\\other_project",
            },
        )

        locations = self._wizard.get_default_configuration_location()
        self.assertEqual(
            locations,
            {
                "darwin": "/Volumes/configs/{0}".format(self.short_test_name),
                "linux2": "/mnt/configs/{0}".format(self.short_test_name),
                "win32": "Z:\\configs\\{0}".format(self.short_test_name),
            },
        )

    def test_get_core_settings(self):
        """
        Ensure we can find the core settings. Given this is a unit test and not
        running off a real core, there's nothing more we can do at the moment.
        """
        # Core is installed as
        # <studio-install>/install/core/python
        # This file is under the equivalent of
        # <studio-install>/install/core/tests/commands_tests/test_project_wizard.py
        # So we have to pop 4 folders to get back the equivalent location.
        install_location = os.path.normpath(
            os.path.join(os.path.dirname(__file__), "..", "..", "..", "..")
        )
        self.assertEqual(
            self._wizard.get_core_settings(),
            {
                "core_path": ShotgunPath.from_current_os_path(
                    install_location
                ).as_system_dict(),
                "localize": True,
                "pipeline_config": None,
                "using_runtime": True,
            },
        )

    def test_execute(self):
        """
        Ensure we can set up the project.
        """
        self._wizard.set_project_disk_name(self.short_test_name)
        path = ShotgunPath.from_current_os_path(
            os.path.join(self.tank_temp, self.short_test_name, "pipeline")
        )
        self._wizard.set_configuration_location(path.linux, path.windows, path.macosx)

        # Upload method not implemented on Mockgun yet, so skip that bit.
        with patch("tank_vendor.shotgun_api3.lib.mockgun.mockgun.Shotgun.upload"):
            with patch("tank.pipelineconfig_utils.get_core_api_version") as api_mock:
                api_mock.return_value = "HEAD"
                self._wizard.execute()