async def test_all_topic_names(client): """Test the get topic_names response.""" salobj.set_random_lsst_dds_partition_prefix() async with salobj.Domain() as domain: domain = salobj.Domain() available_idl_files = list(domain.idl_dir.glob(idl_glob)) names = [ file.name.split("_", )[-1].replace(".idl", "") for file in available_idl_files ] names = names[:conftest.REMOTES_LEN_LIMIT] response = await client.get("/salinfo/topic-names") assert response.status == 200 response_data = await response.json() for name, data in response_data.items(): # assert name in names assert "command_names" in data assert "event_names" in data assert "telemetry_names" in data assert type(data["command_names"]) == list assert type(data["event_names"]) == list assert type(data["telemetry_names"]) == list
async def test_salinfo_metadata(self) -> None: """Test some of the metadata in SalInfo. The main tests of the IDL parser are elsewhere. """ async with salobj.Domain() as domain: salinfo = salobj.SalInfo(domain=domain, name="Test") # Check some topic and field metadata for topic_name, topic_metadata in salinfo.metadata.topic_info.items( ): assert topic_name == topic_metadata.sal_name for field_name, field_metadata in topic_metadata.field_info.items( ): assert field_name == field_metadata.name some_expected_topic_names = ( "command_enable", "command_setArrays", "command_setScalars", "logevent_arrays", "logevent_scalars", "arrays", "scalars", ) assert set(some_expected_topic_names).issubset( set(salinfo.metadata.topic_info.keys()))
async def test_run_command_line(self): exe_name = "run_one_script.py" exe_path = shutil.which(exe_name) if exe_path is None: self.fail( f"Could not find bin script {exe_name}; did you setup and scons this package?" ) index = 135 script = DATA_DIR / "standard" / "subdir" / "script3" config_path = DATA_DIR / "config1.yaml" async with salobj.Domain() as domain, salobj.Remote( domain=domain, name="Script", index=index) as remote: process = await asyncio.create_subprocess_exec( exe_name, str(script), "--config", str(config_path), "--index", str(index), "--loglevel", "10", ) try: t0 = time.monotonic() await asyncio.wait_for(process.wait(), timeout=STD_TIMEOUT) dt = time.monotonic() - t0 print(f"It took {dt:0.2f} seconds to run the script") except Exception: if process.returncode is None: process.terminate() raise final_state = remote.evt_state.get() assert final_state.state == ScriptState.DONE
async def test_class_creation_speed(self) -> None: """Test the speed of creating topic classes on the fly.""" async with salobj.Domain() as domain: t0 = time.monotonic() salinfo = salobj.SalInfo(domain, "Test", index=self.index) topic_names = ( ["logevent_" + name for name in salinfo.event_names] + ["command_" + name for name in salinfo.command_names] + list(salinfo.telemetry_names) ) for topic_name in topic_names: revname = salinfo.revnames.get(topic_name) ddsutil.make_dds_topic_class( parsed_idl=salinfo.parsed_idl, revname=revname ) dt = time.monotonic() - t0 ntopics = len(topic_names) creation_speed = ntopics / dt print( f"Created {creation_speed:0.1f} topic classes/sec ({ntopics} topic classes); " f"total duration {dt:0.2f} seconds." ) self.insert_measurement( verify.Measurement( "salobj.CreateClasses", creation_speed * u.ct / u.second ) )
async def test_make_ack_cmd(self) -> None: async with salobj.Domain() as domain: salinfo = salobj.SalInfo(domain=domain, name="Test") # Use all defaults seqNum = 55 ack = salobj.SalRetCode.CMD_COMPLETE ackcmd = salinfo.make_ackcmd(private_seqNum=seqNum, ack=ack) assert ackcmd.private_seqNum == seqNum assert ackcmd.ack == ack assert ackcmd.error == 0 assert ackcmd.result == "" # Specify an error code and result seqNum = 27 ack = salobj.SalRetCode.CMD_FAILED error = 127 result = "why not?" ackcmd = salinfo.make_ackcmd( private_seqNum=seqNum, ack=ack, error=error, result=result, ) assert ackcmd.private_seqNum == seqNum assert ackcmd.ack == ack assert ackcmd.error == error assert ackcmd.result == result
async def test_do_callbacks_true(self) -> None: index = next(index_gen) async with salobj.Domain() as domain, salobj.SalInfo( domain=domain, name="Test", index=index) as salinfo: command_names = salinfo.command_names # Build a controller and check that callbacks are asigned. async with ControllerWithDoMethods(command_names) as controller: for cmd_name in command_names: with self.subTest(cmd_name=cmd_name): cmd = getattr(controller, "cmd_" + cmd_name) assert cmd.has_callback # do_setAuthList and do_setLogLevel are provided by Controller skip_names = {"setAuthList", "setLogLevel"} for missing_name in command_names: if missing_name in skip_names: continue with self.subTest(missing_name=missing_name): bad_names = [ name for name in command_names if name != missing_name ] with pytest.raises(TypeError): ControllerWithDoMethods(bad_names) extra_names = list(command_names) + ["extra_command"] with pytest.raises(TypeError): ControllerWithDoMethods(extra_names)
async def check_executable(self, script_path): """Check that an executable script can be launched. Parameter --------- script_path : `str` Full path to script. """ salobj.set_random_lsst_dds_partition_prefix() index = self.next_index() script_path = pathlib.Path(script_path).resolve() assert script_path.is_file() async with salobj.Domain() as domain, salobj.Remote( domain=domain, name="Script", index=index) as remote: initial_path = os.environ["PATH"] try: os.environ["PATH"] = str( script_path.parent) + ":" + initial_path process = await asyncio.create_subprocess_exec( str(script_path), str(index)) state = await remote.evt_state.next(flush=False, timeout=MAKE_TIMEOUT) assert state.state == Script.ScriptState.UNCONFIGURED finally: process.terminate() os.environ["PATH"] = initial_path
async def test_negative_lsst_dds_historysync(self) -> None: """Test that setting LSST_DDS_HISTORYSYNC < 0 prevents waiting for historical data. This setting applies to SalInfo, not Remote, but it requires some topics in order to be tested, and Remote provides topics. """ index = next(index_gen) async with salobj.Domain() as domain: # Make a normal remote that waits for historical data. remote1 = salobj.Remote(domain=domain, name="Test", index=index) await asyncio.wait_for(remote1.start_task, timeout=STD_TIMEOUT) assert len(remote1.salinfo.wait_history_isok) > 0 # Make a remote that does not wait for historical data # by defining the history timeout env variable < 0. os.environ[HISTORY_TIMEOUT_NAME] = "-1" try: remote2 = salobj.Remote(domain=domain, name="Test", index=index) await asyncio.wait_for(remote2.start_task, timeout=STD_TIMEOUT) finally: if INITIAL_HISTORY_TIMEOUT is None: del os.environ[HISTORY_TIMEOUT_NAME] else: os.environ[HISTORY_TIMEOUT_NAME] = INITIAL_HISTORY_TIMEOUT assert len(remote2.salinfo.wait_history_isok) == 0
async def main(): logging.info("main method") async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="GenericCamera", index=args.index) logging.info(f"starting remote with index {args.index}") await remote.start_task logging.info("starting CSC") await remote.cmd_start.set_start(timeout=120) logging.info("disabling") await salobj.set_summary_state( remote=remote, state=salobj.State.DISABLED, timeout=120 ) logging.info("enabling") await salobj.set_summary_state( remote=remote, state=salobj.State.ENABLED, timeout=120 ) logging.info("taking a picture") await remote.cmd_takeImages.set_start( numImages=1, expTime=2.0, shutter=True, sensors="", keyValueMap="", obsNote="image", ) logging.info("disabling again") await salobj.set_summary_state( remote=remote, state=salobj.State.DISABLED, timeout=120 ) logging.info("offline") await salobj.set_summary_state( remote=remote, state=salobj.State.OFFLINE, timeout=120 )
async def test_lsst_dds_partition_prefix_required(self) -> None: # Delete LSST_DDS_PARTITION_PREFIX. This should prevent # constructing a Domain with utils.modify_environ(LSST_DDS_PARTITION_PREFIX=None): async with salobj.Domain() as domain: with pytest.raises(RuntimeError): salobj.SalInfo(domain=domain, name="Test", index=1)
async def test_successful_command(client, *args, **kwargs): # Arrange remote = salobj.Remote(domain=salobj.Domain(), name="LOVE") await remote.start_task observing_log_msg = { "user": "******", "message": "a message", } # Act remote.evt_observingLog.flush() response = await client.post("/lovecsc/observinglog", data=json.dumps(observing_log_msg)) # Assert assert response.status == 200 response = await response.json() assert response["ack"] == "Added new observing log to SAL" result = await remote.evt_observingLog.next(flush=False) assert result.user == "an user" assert result.message == "a message" # Clean up await remote.close()
async def test_all_names(self): async with salobj.Domain() as domain: remote = salobj.Remote( domain=domain, name="Test", index=self.index, readonly=True, include=(), start=False, ) topic_names = [ f"evt_{name}" for name in remote.salinfo.event_names ] topic_names += [ f"tel_{name}" for name in remote.salinfo.telemetry_names ] # Check that no topics have been added yet for name in topic_names: self.assertFalse(hasattr(remote, name)) wrapper = watcher.base.RemoteWrapper(remote=remote, topic_names=topic_names) desired_attr_name = (remote.salinfo.name.lower() + "_" + str(remote.salinfo.index)) self.assertEqual(wrapper.attr_name, desired_attr_name) # Check that all topics have been added for name in topic_names: self.assertTrue(hasattr(remote, name)) wrapper_dir = set(dir(wrapper)) self.assertTrue(set(topic_names).issubset(wrapper_dir)) await asyncio.wait_for(remote.start(), timeout=LONG_TIMEOUT) # Check that the initial value for each topic is None. for name in topic_names: self.assertIsNone(getattr(wrapper, name)) # Write one event and one telemetry topic evt_scalars_writer = salobj.topics.ControllerEvent( salinfo=remote.salinfo, name="scalars") tel_scalars_writer = salobj.topics.ControllerTelemetry( salinfo=remote.salinfo, name="scalars") evtint = -3 telint = 47 evt_scalars_writer.set_put(int0=evtint) tel_scalars_writer.set_put(int0=telint) # Wait for the read topics to read the data. await remote.evt_scalars.next(flush=False, timeout=STD_TIMEOUT) await remote.tel_scalars.next(flush=False, timeout=STD_TIMEOUT) # Verify that the wrapper produces the expected values. self.assertEqual(wrapper.evt_scalars.int0, evtint) self.assertEqual(wrapper.tel_scalars.int0, telint)
async def test_start_false(self) -> None: """Test the start argument of Remote.""" index = next(index_gen) async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="Test", index=index, start=False) assert not hasattr(remote, "start_task")
async def run_command(self): async with salobj.Domain() as domain: arc = salobj.Remote(domain=domain, name=self.device_name, index=0) await arc.start_task try: cmd = getattr(arc, f"cmd_{self.command}") await cmd.set_start(timeout=self.timeout) except Exception as e: print(e)
async def test_some_topic_names(client): """Test the use of query params to get only some of the topic_names.""" salobj.set_random_lsst_dds_partition_prefix() async with salobj.Domain() as domain: domain = salobj.Domain() available_idl_files = list(domain.idl_dir.glob(idl_glob)) names = [ file.name.split("_", )[-1].replace(".idl", "") for file in available_idl_files ] names = names[:conftest.REMOTES_LEN_LIMIT] # Get all combinations of categories: categories = ["command", "event", "telemetry"] combs = chain.from_iterable( combinations(categories, r) for r in range(len(categories) + 1)) for comb in combs: # Get categories to be requested and not to be requested requested = list(comb) non_req = list(set(categories) - set(requested)) query_param = "-".join(requested) # Requeste them response = await client.get("/salinfo/topic-names?categories=" + query_param) assert response.status == 200 response_data = await response.json() # If query_params is empty no filtering is applied: if len(requested) == 0: requested = categories non_req = [] for _, data in response_data.items(): # Assert that requested categories are in the response for r in requested: key = r + "_names" assert key in data assert type(data[key]) == list # Assert that non-requested categories are NOT in the response for nr in non_req: key = nr + "_names" assert key not in data
async def test_evt_max_history(self) -> None: """Test non-default evt_max_history Remote constructor argument.""" evt_max_history = 0 index = next(index_gen) async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="Test", index=index, evt_max_history=evt_max_history) self.assert_max_history(remote, evt_max_history=evt_max_history)
async def shutdown(opts): end_state = getattr(salobj.State, opts.state.upper()) domain = salobj.Domain() try: remote = salobj.Remote(domain=domain, name="DSM", index=opts.index) await remote.start_task await salobj.set_summary_state(remote, end_state) finally: await domain.close()
def make_comcam_remotes(): d = salobj.Domain() CCArchiver = salobj.Remote(d, 'CCArchiver') CCCamera = salobj.Remote(d, 'CCCamera') CCHeaderService = salobj.Remote(d, 'CCHeaderService') return { "CCArchiver": CCArchiver, "CCCamera": CCCamera, "CCHeaderService": CCHeaderService }
async def test_salinfo_constructor(self) -> None: with pytest.raises(TypeError): salobj.SalInfo(domain=None, name="Test") async with salobj.Domain() as domain: with pytest.raises(RuntimeError): salobj.SalInfo(domain=domain, name="invalid_component_name") for invalid_index in (1.1, "one"): with pytest.raises(TypeError): salobj.SalInfo(domain=domain, name="Test", index=invalid_index) index = next(index_gen) salinfo = salobj.SalInfo(domain=domain, name="Test", index=index) assert salinfo.name == "Test" assert salinfo.index == index assert not salinfo.start_task.done() assert not salinfo.done_task.done() assert not salinfo.started with pytest.raises(RuntimeError): salinfo.assert_started() asyncio.create_task(salinfo.start()) # Use a short time limit because there are no topics to read await asyncio.wait_for(salinfo.start_task, timeout=STD_TIMEOUT) assert salinfo.start_task.done() assert not salinfo.done_task.done() assert salinfo.started salinfo.assert_started() with pytest.raises(RuntimeError): await salinfo.start() await asyncio.wait_for(salinfo.close(), timeout=STD_TIMEOUT) assert salinfo.start_task.done() assert salinfo.done_task.done() assert salinfo.started salinfo.assert_started() # Test enum index class SalIndex(enum.IntEnum): ONE = 1 TWO = 2 salinfo = salobj.SalInfo(domain=domain, name="Script", index=SalIndex.ONE) assert isinstance(salinfo.index, SalIndex) assert salinfo.index == SalIndex.ONE
async def test_write_only(self) -> None: async with salobj.Domain() as domain: salinfo = salobj.SalInfo(domain=domain, name="Test", write_only=True) # Cannot add a read topic to a write-only SalInfo with pytest.raises(RuntimeError): salobj.topics.ReadTopic(salinfo=salinfo, attr_name="evt_summaryState", max_history=0) await salinfo.start() assert salinfo._read_loop_task.done()
async def start_cmd(request): nonlocal domain data = await request.json() try: assert "csc" in data assert "salindex" in data assert "cmd" in data assert "params" in data except AssertionError: return web.json_response( { "ack": f"Request must have JSON data with the following " f"keys: csc, salindex, cmd_name, params. Received {json.dumps(data)}" }, status=400, ) csc = data["csc"] salindex = data["salindex"] cmd_name = data["cmd"] params = data["params"] remote_name = f"{csc}.{salindex}" # Only create domain if it does not already exist. if domain is None: print("Creating salobj.Domain()") domain = salobj.Domain() domain.default_identity = "LOVE" # Only create remote if it does not exist already. if remote_name not in remotes: print(f"Creating remote {remote_name}.") # Create remote for commanding only, exclude all events and # telemetry topics remotes[remote_name] = salobj.Remote(domain, csc, salindex, include=[]) await remotes[remote_name].start_task cmd = getattr(remotes[remote_name], cmd_name) cmd.set(**params) try: cmd_result = await cmd.start(timeout=5) return web.json_response({"ack": cmd_result.result}) except salobj.AckTimeoutError as e: msg = ( "No ack received from component." if e.ackcmd == salobj.SalRetCode.CMD_NOACK else f"Last ack received {e.ackcmd}." ) return web.json_response({"ack": f"Command time out. {msg}"}, status=504)
async def test_domain_attr(self) -> None: async with salobj.Domain() as domain: assert domain.origin == os.getpid() assert domain.user_host == salobj.get_user_host() assert domain.default_identity == domain.user_host assert domain.ackcmd_qos_set.profile_name == "AckcmdProfile" assert domain.command_qos_set.profile_name == "CommandProfile" assert domain.event_qos_set.profile_name == "EventProfile" assert domain.telemetry_qos_set.profile_name == "TelemetryProfile" assert domain.ackcmd_qos_set.volatile assert domain.command_qos_set.volatile assert not domain.event_qos_set.volatile assert domain.telemetry_qos_set.volatile
async def test_default_authorize(self) -> None: """Test that LSST_DDS_ENABLE_AUTHLIST correctly sets the default_authorize attribute. """ async with salobj.Domain() as domain: for env_var_value in ("0", "1", None, "2", "", "00"): expected_default_authorize = True if env_var_value == "1" else False index = next(index_gen) with utils.modify_environ( LSST_DDS_ENABLE_AUTHLIST=env_var_value): salinfo = salobj.SalInfo(domain=domain, name="Test", index=index) assert salinfo.default_authorize == expected_default_authorize
async def test_metadata(client): """Test the get metadata response.""" salobj.set_random_lsst_dds_partition_prefix() async with salobj.Domain() as domain: domain = salobj.Domain() available_idl_files = list(domain.idl_dir.glob(idl_glob)) names = [ file.name.split("_", )[-1].replace(".idl", "") for file in available_idl_files ] names = names[:conftest.REMOTES_LEN_LIMIT] response = await client.get("/salinfo/metadata") assert response.status == 200 response_data = await response.json() for name, data in response_data.items(): # assert name in names assert "sal_version" in data assert "xml_version" in data assert data["sal_version"].count(".") == 2 assert data["xml_version"].count(".") == 2
async def test_constructor_error(self): async with salobj.Domain() as domain: remote = salobj.Remote(domain=domain, name="Test", index=self.index, readonly=True, start=False) for bad_topic_names in ( ["noprefix"], ["evb_incorrectprefix"], ["evt_nosuchevent"], ["tel_nosuchtelemetry"], ["evt_summaryState", "evt_nosuchevent"], ["tel_scalars", "tel_nosuchtelemetry"], ): with self.subTest(bad_topic_names=bad_topic_names): with self.assertRaises(ValueError): watcher.base.RemoteWrapper(remote=remote, topic_names=bad_topic_names)
def make_maintel_remotes(): d = salobj.Domain() MTMount = salobj.Remote(d, "NewMTMount") MTPtg = salobj.Remote(d, "MTPtg") MTAOS = salobj.Remote(d, "MTAOS") M2 = salobj.Remote(d, "MTM2") M1M3 = salobj.Remote(d, "MTM1M3") M2Hex = salobj.Remote(d, "Hexapod", index=2) CamHex = salobj.Remote(d, "Hexapod", index=1) Rotator = salobj.Remote(d, "Rotator") return { "MTMount": MTMount, "MTPtg": MTPtg, "MTAOS": MTAOS, "M2": M2, "M1M3": M1M3, "M2Hex": M2Hex, "CamHex": CamHex, "Rotator": Rotator }
async def test_some_names(self): """Test wrappers that wrap a subset of names.""" async with salobj.Domain() as domain: remote = salobj.Remote( domain=domain, name="Test", index=self.index, readonly=True, include=(), start=False, ) event_names = [ f"evt_{name}" for name in remote.salinfo.event_names ] telemetry_names = [ f"tel_{name}" for name in remote.salinfo.telemetry_names ] # Check that no topics have been added yet. for name in event_names + telemetry_names: self.assertFalse(hasattr(remote, name)) evt_wrapper = watcher.base.RemoteWrapper(remote=remote, topic_names=event_names) tel_wrapper = watcher.base.RemoteWrapper( remote=remote, topic_names=telemetry_names) # Check that all topics have been added to the remote. for name in event_names + telemetry_names: self.assertTrue(hasattr(remote, name)) # Check that the event wrapper has all the event names # and none of the telemetry names, and vice-versa. evt_wrapper_dir = set(dir(evt_wrapper)) tel_wrapper_dir = set(dir(tel_wrapper)) self.assertTrue(set(event_names).issubset(evt_wrapper_dir)) self.assertTrue(set(telemetry_names).issubset(tel_wrapper_dir)) self.assertEqual(set(event_names) & tel_wrapper_dir, set()) self.assertEqual(set(telemetry_names) & evt_wrapper_dir, set())
async def test_log_level(self) -> None: """Test that log level is decreased (verbosity increased) to INFO.""" log = logging.getLogger() log.setLevel(logging.WARNING) salinfos = [] async with salobj.Domain() as domain: try: # Log level is WARNING; test that log level is decreased # (verbosity increased) to INFO. salinfo = salobj.SalInfo(domain=domain, name="Test") salinfos.append(salinfo) assert salinfo.log.getEffectiveLevel() == logging.INFO # Start with log level DEBUG and test that log level # is unchanged. salinfo.log.setLevel(logging.DEBUG) salinfo = salobj.SalInfo(domain=domain, name="Test") salinfos.append(salinfo) assert salinfo.log.getEffectiveLevel() == logging.DEBUG finally: for salinfo in salinfos: await salinfo.close()
async def test_ack_error_repr(self) -> None: """Test AckError.__str__ and AckError.__repr__""" async with salobj.Domain() as domain: salinfo = salobj.SalInfo(domain, "Test", index=1) msg = "a message" private_seqNum = 5 ack = 23 error = -6 result = "a result" err = salobj.AckError( msg, ackcmd=salinfo.make_ackcmd(private_seqNum=private_seqNum, ack=ack, error=error, result=result), ) str_err = str(err) for item in (msg, private_seqNum, ack, error, result): assert str(item) in str_err assert "AckError" not in str_err repr_err = repr(err) for item in ("AckError", msg, private_seqNum, ack, error, result): assert str(item) in repr_err
async def make_remote_and_topic_writer( self, ) -> typing.AsyncGenerator[salobj.Remote, None]: """Make a remote and launch a topic writer in a subprocess. Return the remote. """ script_path = self.datadir / "topic_writer.py" process = await asyncio.create_subprocess_exec( str(script_path), str(self.index) ) try: async with salobj.Domain() as domain, salobj.Remote( domain=domain, name="Test", index=self.index ) as remote: yield remote await salobj.set_summary_state( remote=remote, state=salobj.State.OFFLINE, timeout=STD_TIMEOUT ) await asyncio.wait_for(process.wait(), timeout=STD_TIMEOUT) finally: if process.returncode is None: print("Warning: killing the topic writer") process.kill()