def test_presets(self): """Test that file overrides can set command line options in bulk. """ with self.runner.isolated_filesystem(): result = self.runner.invoke(butler.cli, ["create", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) overrides_path = os.path.join(TESTDIR, "data", "config-overrides.yaml") # Run with a presets file result = self.runner.invoke(butler.cli, ["config-dump", "here", "--options-file", overrides_path]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) cfg = yaml.safe_load(result.stdout) # Look for datastore information self.assertIn("formatters", cfg) self.assertIn("root", cfg) # Now run with an explicit subset and presets result = self.runner.invoke(butler.cli, ["config-dump", "here", f"-@{overrides_path}", "--subset", ".registry"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) cfg = yaml.safe_load(result.stdout) # Look for datastore information self.assertNotIn("formatters", cfg) self.assertIn("managers", cfg) # Now with subset before presets -- explicit always trumps # presets. result = self.runner.invoke(butler.cli, ["config-dump", "here", "--subset", ".registry", "--options-file", overrides_path]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) cfg = yaml.safe_load(result.stdout) # Look for datastore information self.assertNotIn("formatters", cfg) self.assertIn("managers", cfg)
def test_cli(self): mock = MagicMock() @click.command() @click.option("--value", callback=split_kv, multiple=True) def cli(value): mock(value) result = self.runner.invoke(cli, ["--value", "first=1"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({'first': '1'}) result = self.runner.invoke(cli, ["--value", "first=1,second=2"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({'first': '1', 'second': '2'}) result = self.runner.invoke( cli, ["--value", "first=1", "--value", "second=2"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({'first': '1', 'second': '2'}) # double separator "==" should fail: result = self.runner.invoke(cli, ["--value", "first==1"]) self.assertEqual(result.exit_code, 1) self.assertEqual( result.output, "Error: Could not parse key-value pair 'first==1' using separator '=', with " "multiple values allowed.\n")
def testNonExistantConfigFile(self): """Verify an expected error when a given config override file does not exist. """ with self.runner.isolated_filesystem(): result = self.runner.invoke(butlerCli, ["create", "repo"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke( butlerCli, ["register-skymap", "repo", "--config-file", "foo.py"]) # foo.py does not exist; exit could should be non-zero. self.assertNotEqual(result.exit_code, 0, clickResultMsg(result))
def test_file(self): """test dumping the config to a file.""" with self.runner.isolated_filesystem(): result = self.runner.invoke(butler.cli, ["create", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(butler.cli, ["config-dump", "here", "--file=there"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # check for some expected keywords: with open("there", "r") as f: cfg = yaml.safe_load(f) self.assertIn("datastore", cfg) self.assertIn("composites", cfg["datastore"]) self.assertIn("storageClasses", cfg)
def test_stdout(self): """Test dumping the config to stdout.""" with self.runner.isolated_filesystem(): result = self.runner.invoke(butler.cli, ["create", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # test dumping to stdout: result = self.runner.invoke(butler.cli, ["config-dump", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # check for some expected keywords: cfg = yaml.safe_load(result.stdout) self.assertIn("datastore", cfg) self.assertIn("composites", cfg["datastore"]) self.assertIn("storageClasses", cfg)
def test_help(self): """Verify expected help text output. Verify argument help gets inserted after the usage, in the order arguments are declared. Verify that MWArgument adds " ..." after the option metavar when `nargs` != 1. The default behavior of click is to add elipsis when nargs does not equal 1, but it does not put a space before the elipsis and we prefer a space between the metavar and the elipsis.""" # nargs can be -1 for any number of args, or >= 1 for a specified # number of arguments. helpText = "Things help text." for numberOfArgs in (-1, 1, 2): for required in (True, False): @click.command() @self.things_argument(required=required, nargs=numberOfArgs, help=helpText) @self.other_argument() def cmd(things, other): """Cmd help text.""" pass result = self.runner.invoke(cmd, ["--help"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) expectedOutut = (f"""Usage: cmd [OPTIONS] {'THINGS' if required else '[THINGS]'} {'... ' if numberOfArgs != 1 else ''}OTHER Cmd help text. {helpText} {self.otherHelpText} """) self.assertIn(expectedOutut, result.output)
def runTest(self, cmd): """Test that the log context manager works with the butler cli to initialize the logging system according to cli inputs for the duration of the command execution and resets the logging system to its previous state or expected state when command execution finishes.""" pyRoot = self.PythonLogger(None) pyButler = self.PythonLogger("lsst.daf.butler") lsstRoot = self.LsstLogger("") lsstButler = self.LsstLogger("lsst.daf.butler") with command_test_env(self.runner, "lsst.daf.butler.tests.cliLogTestBase", "command-log-settings-test"): result = cmd() self.assertEqual(result.exit_code, 0, clickResultMsg(result)) if lsstLog is not None: self.assertFalse(hasLsstLogHandler(logging.getLogger()), msg="CliLog should remove the lsst.log handler it added to the root logger.") self.assertEqual(pyRoot.logger.level, logging.INFO) self.assertEqual(pyButler.logger.level, pyButler.initialLevel) if lsstLog is not None: self.assertEqual(lsstRoot.logger.getLevel(), lsstLog.INFO) # lsstLogLevel can either be the inital level, or uninitialized or # the defined default value. expectedLsstLogLevel = ((lsstButler.initialLevel, ) if lsstButler.initialLevel != -1 else(-1, CliLog.defaultLsstLogLevel)) self.assertIn(lsstButler.logger.getLevel(), expectedLsstLogLevel)
def test_subset(self): """Test selecting a subset of the config.""" with self.runner.isolated_filesystem(): result = self.runner.invoke(butler.cli, ["create", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(butler.cli, ["config-dump", "here", "--subset", "datastore"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) cfg = yaml.safe_load(result.stdout) # count the keys in the datastore config self.assertEqual(len(cfg), 8) self.assertIn("cls", cfg) self.assertIn("create", cfg) self.assertIn("formatters", cfg) self.assertIn("records", cfg) self.assertIn("root", cfg) self.assertIn("templates", cfg)
def testCollection(self): butler = Butler(self.root, run="foo") # try replacing the testRepo's butler with the one with the "foo" run. self.testRepo.butler = butler self.testRepo.butler.registry.insertDimensionData( "visit", { "instrument": "DummyCamComp", "id": 425, "name": "fourtwentyfive", "physical_filter": "d-r", "visit_system": 1 }) self.testRepo.addDataset(dataId={ "instrument": "DummyCamComp", "visit": 425 }, run="foo") # verify getting records from the "ingest/run" collection result = self.runner.invoke(butlerCli, [ "query-dimension-records", self.root, "visit", "--collections", "ingest/run", "--datasets", "test_metric_comp" ]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) rows = array( (("DummyCamComp", "423", "d-r", "1", "fourtwentythree", "None", "None", "None", "None", "None", "None", "None", "None .. None"), ("DummyCamComp", "424", "d-r", "1", "fourtwentyfour", "None", "None", "None", "None", "None", "None", "None", "None .. None"))) expected = AstropyTable(rows, names=self.expectedColumnNames) self.assertAstropyTablesEqual(readTable(result.output), expected) # verify getting records from the "foo" collection result = self.runner.invoke(butlerCli, [ "query-dimension-records", self.root, "visit", "--collections", "foo", "--datasets", "test_metric_comp" ]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) rows = array((("DummyCamComp", "425", "d-r", "1", "fourtwentyfive", "None", "None", "None", "None", "None", "None", "None", "None .. None"), )) expected = AstropyTable(rows, names=self.expectedColumnNames) self.assertAstropyTablesEqual(readTable(result.output), expected)
def test_click_disabled_by_default(self): """Test that progress is disabled by default in click commands. """ result = self.runner.invoke( self.get_cmd(logging.INFO, enabled=False), [], ) self.assertEqual(result.exit_code, 0, clickResultMsg(result))
def test_callMock(self): """Test that a mocked subcommand calls the Mocker and can be verified. """ runner = LogCliRunner(env=mockEnvVar) result = runner.invoke(butler.cli, ["create", "repo"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) Mocker.mock.assert_called_with(repo="repo", seed_config=None, standalone=False, override=False, outfile=None)
def test_click_disabled_globally(self): """Test turning on progress in click commands. """ result = self.runner.invoke( self.get_cmd(logging.INFO, enabled=False), ["--no-progress"], ) self.assertEqual(result.exit_code, 0, clickResultMsg(result))
def testClobber(self): runner = LogCliRunner() with runner.isolated_filesystem(): destdir = "tmp2/" result = runner.invoke(cli, ["retrieve-artifacts", self.root, destdir]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # Running again should fail result = runner.invoke(cli, ["retrieve-artifacts", self.root, destdir]) self.assertNotEqual(result.exit_code, 0, clickResultMsg(result)) # But with clobber should pass result = runner.invoke( cli, ["retrieve-artifacts", self.root, destdir, "--clobber"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result))
def test_invalidSubset(self): """Test selecting a subset key that does not exist in the config.""" with self.runner.isolated_filesystem(): result = self.runner.invoke(butler.cli, ["create", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # test dumping to stdout: result = self.runner.invoke(butler.cli, ["config-dump", "here", "--subset", "foo"]) self.assertEqual(result.exit_code, 1) self.assertEqual(result.exception.args, KeyError('foo not found in config at here').args)
def test_click_disabled_by_log_level(self): """Test that progress reports below the current log level are disabled, even if progress is globally enabled. """ result = self.runner.invoke( self.get_cmd(logging.DEBUG, enabled=False), ["--progress"], ) self.assertEqual(result.exit_code, 0, clickResultMsg(result))
def testPruneTagged(self): result = self.runner.invoke(butlerCli, ["query-collections", self.root]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) expected = Table(array((("ingest/run", "RUN"), ("ingest", "TAGGED"))), names=("Name", "Type")) self.assertAstropyTablesEqual(readTable(result.output), expected) # Try pruning TAGGED, should succeed. result = self.runner.invoke( butlerCli, ["prune-collection", self.root, "ingest", "--unstore"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(butlerCli, ["query-collections", self.root]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) expected = Table((["ingest/run"], ["RUN"]), names=("Name", "Type")) self.assertAstropyTablesEqual(readTable(result.output), expected)
def test_choice(self): choices = ["FOO", "BAR", "BAZ"] mock = MagicMock() @click.command() @click.option("--metasyntactic-var", callback=partial(split_kv, unseparated_okay=True, choice=click.Choice( choices, case_sensitive=False), normalize=True)) def cli(metasyntactic_var): mock(metasyntactic_var) # check a valid choice without a kv separator result = self.runner.invoke(cli, ["--metasyntactic-var", "FOO"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({"": "FOO"}) # check a valid choice with a kv separator result = self.runner.invoke( cli, ["--metasyntactic-var", "lsst.daf.butler=BAR"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({"lsst.daf.butler": "BAR"}) # check invalid choices with and wihtout kv separators for val in ("BOZ", "lsst.daf.butler=BOZ"): result = self.runner.invoke(cli, ["--metasyntactic-var", val]) self.assertNotEqual(result.exit_code, 0, msg=clickResultMsg(result)) self.assertRegex( result.output, r"Error: Invalid value for ['\"]\-\-metasyntactic-var['\"]:") self.assertIn( f" invalid choice: BOZ. (choose from {', '.join(choices)})", result.output) # check value normalization (lower case "foo" should become "FOO") result = self.runner.invoke( cli, ["--metasyntactic-var", "lsst.daf.butler=foo"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({"lsst.daf.butler": "FOO"})
def testNoConfigOverride(self): """Verify expected arguments are passed to makeSkyMap with no config overrides.""" with self.runner.isolated_filesystem(): result = self.runner.invoke(butlerCli, ["create", "repo"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) with unittest.mock.patch( "lsst.pipe.tasks.script.registerSkymap.makeSkyMap" ) as mock: # call without any config overrides result = self.runner.invoke(butlerCli, ["register-skymap", "repo"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) expectedConfig = registerSkymap.MakeSkyMapConfig() mock.assert_called_once() # assert that the first argument to the call to makeSkyMap was a butler self.assertIsInstance(mock.call_args[0][0], Butler) # assert that the second argument matches the expected config self.assertEqual(mock.call_args[0][1], expectedConfig)
def test_separatorFunctoolsDash(self): mock = MagicMock() @click.command() @click.option("--value", callback=partial(split_kv, separator="-"), multiple=True) def cli(value): mock(value) result = self.runner.invoke(cli, ["--value", "first-1", "--value", "second-2"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({'first': '1', 'second': '2'})
def testPruneCollections(self): """Test removing a collection and run from a repository using the butler prune-collection subcommand.""" with self.runner.isolated_filesystem(): repoName = "myRepo" runName = "myRun" taggedName = "taggedCollection" # Add the run and the tagged collection to the repo: result = self.runner.invoke(butlerCli, ["create", repoName]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # Use the butler initalizer to create the run, then create a tagged # collection. butler = Butler(repoName, run=runName) butler.registry.registerCollection(taggedName, CollectionType.TAGGED) # Verify the run and tag show up in query-collections: result = self.runner.invoke(butlerCli, ["query-collections", repoName]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertIn(runName, result.output) self.assertIn(taggedName, result.output) # Verify the tagged collection can be removed: result = self.runner.invoke( butlerCli, ["prune-collection", repoName, taggedName, "--unstore"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(butlerCli, ["query-collections", repoName]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertIn(runName, result.output) self.assertNotIn(taggedName, result.output) # Verify the run can be removed: result = self.runner.invoke(butlerCli, [ "prune-collection", repoName, runName, "--purge", "--unstore" ]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertNotIn(runName, result.output) self.assertNotIn(taggedName, result.output)
def test_section_function(self): """Verify that the section does not cause any arguments to be passed to the command function. The command function `cli` implementation inputs `foo` and `bar`, but does accept an argument for the section. When the command is invoked and the function called it should result in exit_code=0 (not 1 with a missing argument error). """ result = self.runner.invoke(self.cli, []) self.assertEqual(result.exit_code, 0, clickResultMsg(result))
def testWhere(self): result = self.runner.invoke(butlerCli, [ "query-dimension-records", self.root, "visit", "--where", "instrument='DummyCamComp' AND visit.name='fourtwentythree'" ]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) rows = array((("DummyCamComp", "423", "d-r", "1", "fourtwentythree", "None", "None", "None", "None", "None", "None", "None", "None .. None"), )) expected = AstropyTable(rows, names=self.expectedColumnNames) self.assertAstropyTablesEqual(readTable(result.output), expected)
def testUse(self): """Test using the MWArgumentDecorator with a command.""" mock = MagicMock() @click.command() @self.things_argument() def cli(things): mock(things) self.runner = click.testing.CliRunner() result = self.runner.invoke(cli, ("foo")) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) mock.assert_called_with("foo")
def testUse(self): """Test using the MWOptionDecorator with a command.""" mock = MagicMock() @click.command() @self.test_option() def cli(test): mock(test) self.runner = click.testing.CliRunner() result = self.runner.invoke(cli, ("-t", "foo")) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) mock.assert_called_with(("foo",))
def test_exist(self): """Test the exist argument, verify that True means the file must exist, False means the file must not exist, and None means that the file may or may not exist.""" with self.runner.isolated_filesystem(): mustExistCmd = self.getCmd(exists=True) mayExistCmd = self.getCmd(exists=None) mustNotExistCmd = self.getCmd(exists=False) args = ["--name", "foo.txt"] result = self.runner.invoke(mustExistCmd, args) self.assertNotEqual(result.exit_code, 0, clickResultMsg(result)) self.assertRegex(result.output, """['"]foo.txt['"] does not exist.""") result = self.runner.invoke(mayExistCmd, args) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(mustNotExistCmd, args) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) # isolated_filesystem runs in a temporary directory, when it is # removed everything inside will be removed. with open("foo.txt", "w") as _: result = self.runner.invoke(mustExistCmd, args) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(mayExistCmd, args) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) result = self.runner.invoke(mustNotExistCmd, args) self.assertNotEqual(result.exit_code, 0, clickResultMsg(result)) self.assertIn('"foo.txt" should not exist.', result.output)
def testQueryDatasetTypes(self): self.maxDiff = None datasetName = "test" instrumentDimension = "instrument" visitDimension = "visit" storageClassName = "testDatasetType" expectedNotVerbose = {"datasetTypes": [datasetName]} runner = LogCliRunner() with runner.isolated_filesystem(): butlerCfg = Butler.makeRepo("here") butler = Butler(butlerCfg, writeable=True) storageClass = StorageClass(storageClassName) butler.registry.storageClasses.registerStorageClass(storageClass) dimensions = butler.registry.dimensions.extract( (instrumentDimension, visitDimension)) datasetType = DatasetType(datasetName, dimensions, storageClass) butler.registry.registerDatasetType(datasetType) # check not-verbose output: result = runner.invoke(cli, ["query-dataset-types", "here"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertEqual(expectedNotVerbose, yaml.safe_load(result.output)) # check glob output: result = runner.invoke(cli, ["query-dataset-types", "here", "t*"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertEqual(expectedNotVerbose, yaml.safe_load(result.output)) # check verbose output: result = runner.invoke( cli, ["query-dataset-types", "here", "--verbose"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) response = yaml.safe_load(result.output) # output dimension names contain all required dimensions, more than # the registered dimensions, so verify the expected components # individually. self.assertEqual(response["datasetTypes"][0]["name"], datasetName) self.assertEqual(response["datasetTypes"][0]["storageClass"], storageClassName) self.assertIn(instrumentDimension, response["datasetTypes"][0]["dimensions"]) self.assertIn(visitDimension, response["datasetTypes"][0]["dimensions"])
def testOverride(self): """Test using the MWOptionDecorator with a command and overriding one of the default values.""" mock = MagicMock() @click.command() @self.test_option(multiple=False) def cli(test): mock(test) self.runner = click.testing.CliRunner() result = self.runner.invoke(cli, ("-t", "foo")) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) mock.assert_called_with("foo")
def testRetrieveSubset(self): runner = LogCliRunner() with runner.isolated_filesystem(): destdir = "tmp1/" result = runner.invoke(cli, [ "retrieve-artifacts", self.root, destdir, "--where", "instrument='DummyCamComp' AND visit=423" ]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) self.assertTrue(result.stdout.endswith(": 3\n"), f"Expected 3 got: {result.stdout}") artifacts = self.find_files(destdir) self.assertEqual(len(artifacts), 3, f"Expected 3 artifacts: {artifacts}")
def test_defaults(self, mockAssociate): """Test the expected default values & types for optional options. """ result = self.runner.invoke( butlerCli, ["associate", "myRepo", "myCollection"]) self.assertEqual(result.exit_code, 0, clickResultMsg(result)) mockAssociate.assert_called_once_with( repo="myRepo", collection="myCollection", dataset_type=tuple(), collections=tuple(), where=None, find_first=False )
def test_separatorDash(self): def split_kv_dash(context, param, values): return split_kv(context, param, values, separator="-") mock = MagicMock() @click.command() @click.option("--value", callback=split_kv_dash, multiple=True) def cli(value): mock(value) result = self.runner.invoke(cli, ["--value", "first-1"]) self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) mock.assert_called_with({'first': '1'})