from __future__ import annotations from collections.abc import Callable from copy import deepcopy import json import os import socket import threading import time import unittest from unittest.mock import MagicMock, Mock, patch from cognitive_agent.application.self_extension import ( ExtensionIntegrationError, GatewayError, SelfExtensionService, SimulationGateway, TcpGateway, build_gateway_command, validate_slot, ) from cognitive_agent.application.zcan_seat_executor import ZcanBridgeSeatExecutor class SelfExtensionTests(unittest.TestCase): @staticmethod def _wait_until(predicate: Callable[[], bool], timeout: float = 3.0) -> bool: deadline = time.monotonic() + timeout while time.monotonic() < deadline: if predicate(): return True time.sleep(0.02) return predicate() def test_gateway_command_contracts_and_slot_validation(self) -> None: command = build_gateway_command("learn", {"slot": 17}) self.assertEqual(command["type"], "infrared") self.assertEqual(command["action"], "learn") self.assertEqual(command["data"], {"no": 17}) self.assertTrue(str(command["messageId"]).startswith("bridge-")) self.assertEqual(validate_slot("248"), 248) with self.assertRaises(GatewayError): validate_slot(0) with self.assertRaises(GatewayError): build_gateway_command("erase", {"confirm": "wrong"}) def test_simulation_gateway_executes_without_physical_confirmation(self) -> None: service = SelfExtensionService(SimulationGateway()) result = service.execute_gateway("emit", {"slot": 3, "repeat": 2}) self.assertTrue(result["success"]) self.assertEqual(result["transport"], "simulation") self.assertFalse(result["physical_state_confirmed"]) self.assertEqual(len(result["device_response"]), 2) def test_plan_only_accepts_learned_catalog_capabilities(self) -> None: service = SelfExtensionService(SimulationGateway()) proposed = { "summary": "先开玩具灯,再打开阅读灯", "needs_confirmation": False, "question": "", "steps": [ { "kind": "peripheral", "capability_id": "cap-light-on", "command": "", "label": "ignored", }, { "kind": "peripheral", "capability_id": "invented-capability", "command": "", "label": "ignored", }, { "kind": "vehicle", "capability_id": "", "command": "light reading all on", "label": "车内灯光 · 阅读灯", }, ], } with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False): with patch.object( service, "_request_gpt_plan", return_value=json.dumps(proposed, ensure_ascii=False), ): result = service.plan( { "utterance": "打开玩具灯和阅读灯", "peripherals": [ { "id": "cap-light-on", "status": "learned", "device_name": "灯光测试玩具", "action": "开启", "risk": "low", } ], "vehicle_devices": [ { "name": "车内灯光", "actions": [ { "name": "阅读灯", "command": "light reading all on", } ], } ], } ) self.assertEqual(len(result["steps"]), 2) self.assertEqual(result["steps"][0]["capability_id"], "cap-light-on") self.assertEqual(result["steps"][1]["command"], "light reading all on") def test_plan_rejects_vehicle_command_outside_current_catalog(self) -> None: service = SelfExtensionService(SimulationGateway()) proposed = { "summary": "unsafe target", "needs_confirmation": False, "question": "", "steps": [ { "kind": "vehicle", "capability_id": "", "command": "seat passenger vent high", "label": "wrong seat", "delay_ms": 0, } ], } with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False): with patch.object( service, "_request_gpt_plan", return_value=json.dumps(proposed), ): with self.assertRaisesRegex(ExtensionIntegrationError, "双通道"): service.plan( { "utterance": "打开座椅通风", "peripherals": [], "vehicle_devices": [ { "name": "主驾座椅", "actions": [ { "name": "通风高档", "command": "seat driver vent high", } ], } ], } ) def test_gpt_plan_honors_streaming_responses_configuration(self) -> None: proposed = json.dumps( { "summary": "关闭空调", "needs_confirmation": False, "question": "", "steps": [ { "kind": "vehicle", "capability_id": "", "command": "ac off", "label": "关闭空调", } ], }, ensure_ascii=False, ) stream_body = "\n".join( ( "event: response.output_text.delta", "data: " + json.dumps( { "type": "response.output_text.delta", "delta": proposed[:20], }, ensure_ascii=False, ), "event: response.output_text.done", "data: " + json.dumps( {"type": "response.output_text.done", "text": proposed}, ensure_ascii=False, ), "data: [DONE]", ) ) response = MagicMock() response.__enter__.return_value = response response.read.return_value = stream_body.encode("utf-8") with patch.dict( os.environ, { "OPENAI_STREAM": "true", "IR_OPENAI_MODEL": "gpt-5.6-sol", }, clear=False, ): service = SelfExtensionService(SimulationGateway()) with patch( "cognitive_agent.application.self_extension.urlopen", return_value=response, ) as open_request: output = service._request_gpt_plan( "关闭空调", [], [ { "name": "空调", "actions": [{"name": "关闭空调", "command": "ac off"}], } ], "test-key", ) self.assertEqual(json.loads(output)["steps"][0]["command"], "ac off") request_payload = json.loads( open_request.call_args.args[0].data.decode("utf-8") ) self.assertIs(request_payload["stream"], True) self.assertEqual(request_payload["model"], "gpt-5.6-sol") command_schema = request_payload["text"]["format"]["schema"]["properties"][ "steps" ]["items"]["properties"]["command"] self.assertEqual(command_schema["type"], "string") self.assertEqual(command_schema["maxLength"], 300) def test_plan_accepts_bounded_passenger_position_for_declared_ch0_executor( self, ) -> None: service = SelfExtensionService(SimulationGateway()) proposed = { "summary": "调整副驾", "needs_confirmation": False, "question": "", "steps": [ { "kind": "vehicle", "capability_id": "", "command": "副驾水平调到63%", "label": "副驾水平位置 63%", "delay_ms": 0, } ], } payload = { "utterance": "副驾水平位置调到63%", "peripherals": [], "vehicle_devices": [ { "id": "passenger-seat", "name": "副驾座椅", "channel": "CH0", "actions": [ {"name": "副驾水平 50%", "command": "副驾水平调到50%"} ], } ], } with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False): with patch.object( service, "_request_gpt_plan", return_value=json.dumps(proposed, ensure_ascii=False), ): result = service.plan(payload) self.assertEqual(result["steps"][0]["command"], "副驾水平调到63%") proposed["steps"][0]["command"] = "副驾水平调到101%" with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False): with patch.object( service, "_request_gpt_plan", return_value=json.dumps(proposed, ensure_ascii=False), ): with self.assertRaisesRegex(ExtensionIntegrationError, "双通道"): service.plan(payload) def test_vehicle_execution_reuses_the_same_orchestration_service(self) -> None: with patch.dict( os.environ, {"COGNITIVE_AGENT_EXECUTION_MODE": "dry_run"}, clear=False, ): service = SelfExtensionService(SimulationGateway()) vehicle_service = Mock() vehicle_service.process.return_value = { "status": "succeeded", "provider_used": "local_rules", "request_id": "req-extension-1", "conversation_id": "conv-extension-shared", "conversation_turn": 3, "decision_summary": ["打开阅读灯"], "validation": [], "execution_report": {"spec": {"results": [{"status": "succeeded"}]}}, "deferred_requests": [], } result = service.execute_vehicle( { "command": "打开全部阅读灯", "conversation_id": "conv-extension-shared", "vehicle_id": "denza_n7", }, vehicle_service, hardware_access_bypass=True, ) self.assertTrue(result["success"]) self.assertEqual(result["request_id"], "req-extension-1") self.assertEqual(result["conversation_id"], "conv-extension-shared") self.assertEqual(result["conversation_turn"], 3) self.assertEqual(result["status"], "succeeded") self.assertEqual( result["execution_report"], vehicle_service.process.return_value["execution_report"] ) self.assertEqual(result["operation_results"], [{"status": "succeeded"}]) self.assertFalse(result["physical_feedback_required"]) self.assertFalse(result["physical_feedback_confirmed"]) vehicle_service.process.assert_called_once() self.assertEqual(vehicle_service.process.call_args.args[0], "打开全部阅读灯") self.assertEqual( vehicle_service.process.call_args.args[2], "conv-extension-shared" ) self.assertTrue(vehicle_service.process.call_args.kwargs["hardware_access_bypass"]) def test_vehicle_execution_only_marks_exact_complete_status_as_success(self) -> None: service = SelfExtensionService(SimulationGateway()) for status in ( "validation_failed", "needs_confirmation", "capability_unavailable", "partially_succeeded", ): with self.subTest(status=status): vehicle_service = Mock() vehicle_service.process.return_value = { "status": status, "provider_used": "local_rules", "request_id": f"req-{status}", "decision_summary": [], "validation": [], "execution_report": { "spec": {"results": [{"status": "succeeded"}]} }, "deferred_requests": [], } result = service.execute_vehicle( {"command": "打开全部阅读灯"}, vehicle_service, hardware_access_bypass=True, ) self.assertFalse(result["success"]) self.assertEqual(result["status"], status) def test_vehicle_execution_rejects_failed_or_missing_operation_results(self) -> None: service = SelfExtensionService(SimulationGateway()) for operation_results in ( [], [{"status": "succeeded"}, {"status": "failed", "message": "timeout"}], ): with self.subTest(operation_results=operation_results): vehicle_service = Mock() vehicle_service.process.return_value = { "status": "succeeded", "execution_report": {"spec": {"results": operation_results}}, "domain_state": {"roof": {"shade_position_pct": 31}}, "actuator_state": {"driver": {"heating": {"state": "off"}}}, } result = service.execute_vehicle( {"command": "打开遮阳帘"}, vehicle_service, hardware_access_bypass=True, ) self.assertFalse(result["success"]) self.assertEqual( result["domain_state"], {"roof": {"shade_position_pct": 31}} ) self.assertEqual( result["actuator_state"], {"driver": {"heating": {"state": "off"}}}, ) def test_hardware_vehicle_execution_requires_authoritative_feedback_per_item( self, ) -> None: with patch.dict( os.environ, {"COGNITIVE_AGENT_EXECUTION_MODE": "hardware"}, clear=False, ): service = SelfExtensionService(SimulationGateway()) vehicle_service = Mock() vehicle_service.process.return_value = { "status": "succeeded", "provider_used": "local_rules", "request_id": "req-extension-hardware", "execution_report": { "spec": { "status": "succeeded", "physical_state": { "source": "physical_can_ch1", "complete": True, }, "results": [ { "ref": "operation-1", "domain": "climate", "status": "succeeded", "telemetry": { "channel": 1, "simulated": False, "feedback_required": True, "feedback_observed": True, "feedback_match": True, "feedback_frame": { "frame_id": "0x404", "channel": 1, "payload_size": 8, "data_hex": "00 02 00 00 18 18 00 00", }, "feedback_state": { "source": "physical_can_ch1", "channel": 1, "frame_id": "0x404", "feedback_frame_id": "0x404", "valid": True, "domain_state": {"climate": {"power": "on"}}, }, }, }, { "ref": "operation-2", "domain": "seat", "status": "succeeded", "telemetry": { "channel": 1, "simulated": False, "feedback_required": True, "feedback_observed": True, "feedback_match": True, "feedback_frame": { "frame_id": "0x4FA", "channel": 1, "payload_size": 8, "data_hex": "02 00 00 00 00 00 00 00", }, "feedback_state": { "source": "physical_can_ch1", "channel": 1, "frame_id": "0x4FA", "feedback_frame_id": "0x4FA", "valid": True, "actuator_state": { "driver": { "ventilation": { "state": "on", "level": 1, } } }, }, }, }, ], } }, "domain_state": {"climate": {"power": "on"}}, "actuator_state": {"driver": {"ventilation": {"state": "on"}}}, } confirmed = service.execute_vehicle( {"command": "打开空调"}, vehicle_service, hardware_access_bypass=True, ) self.assertTrue(confirmed["success"]) self.assertTrue(confirmed["physical_feedback_required"]) self.assertTrue(confirmed["physical_feedback_confirmed"]) self.assertEqual(confirmed["physical_state"]["source"], "physical_can_ch1") self.assertEqual(len(confirmed["physical_feedback"]), 2) self.assertTrue(all(item["confirmed"] for item in confirmed["physical_feedback"])) self.assertTrue(all(item["required"] for item in confirmed["physical_feedback"])) self.assertTrue(all(item["observed"] for item in confirmed["physical_feedback"])) self.assertTrue(all(item["match"] is True for item in confirmed["physical_feedback"])) self.assertTrue(all(item["channel"] == 1 for item in confirmed["physical_feedback"])) self.assertEqual( confirmed["physical_feedback"][0]["state"]["source"], "physical_can_ch1", ) operation = vehicle_service.process.return_value["execution_report"]["spec"][ "results" ][1] valid_telemetry = deepcopy(operation["telemetry"]) operation["telemetry"] = {**valid_telemetry, "feedback_match": None} unconfirmed = service.execute_vehicle( {"command": "打开空调"}, vehicle_service, hardware_access_bypass=True, ) self.assertFalse(unconfirmed["success"]) self.assertFalse(unconfirmed["physical_feedback_confirmed"]) self.assertIn("未逐项获得权威物理反馈", unconfirmed["message"]) invalid_cases: list[tuple[str, dict]] = [] for field, value in ( ("channel", 0), ("simulated", True), ("feedback_required", False), ("feedback_observed", False), ("feedback_match", False), ("feedback_frame", None), ("feedback_state", None), ): invalid_cases.append((field, {**valid_telemetry, field: value})) wrong_frame_channel = deepcopy(valid_telemetry) wrong_frame_channel["feedback_frame"]["channel"] = 0 invalid_cases.append(("feedback_frame.channel", wrong_frame_channel)) missing_frame_payload = deepcopy(valid_telemetry) missing_frame_payload["feedback_frame"].pop("data_hex") invalid_cases.append(("feedback_frame.data_hex", missing_frame_payload)) incomplete_frame_payload = deepcopy(valid_telemetry) incomplete_frame_payload["feedback_frame"]["payload_size"] = 7 invalid_cases.append( ("feedback_frame.payload_size", incomplete_frame_payload) ) wrong_source = deepcopy(valid_telemetry) wrong_source["feedback_state"]["source"] = "physical_can_ch0" invalid_cases.append(("feedback_state.source", wrong_source)) missing_decoded_state = deepcopy(valid_telemetry) missing_decoded_state["feedback_state"].pop("actuator_state") invalid_cases.append(("feedback_state.actuator_state", missing_decoded_state)) mismatched_frame = deepcopy(valid_telemetry) mismatched_frame["feedback_state"]["frame_id"] = "0x404" invalid_cases.append(("feedback_state.frame_id", mismatched_frame)) invalid_cases.append( ( "legacy_ch0", { "channel": 0, "simulated": False, "hardware_feedback": True, "feedback_frame": {"frame_id": "0x302"}, }, ) ) for label, insufficient_feedback in invalid_cases: with self.subTest(insufficient_feedback=label): operation["telemetry"] = insufficient_feedback rejected = service.execute_vehicle( {"command": "打开空调"}, vehicle_service, hardware_access_bypass=True, ) self.assertFalse(rejected["success"]) self.assertFalse(rejected["physical_feedback_confirmed"]) def test_ch0_passenger_feedback_requires_shared_runtime_physical_snapshot( self, ) -> None: operation = { "ref": "passenger-longitudinal-63", "domain": "seat", "status": "succeeded", "telemetry": { "transport": "shared_can_runtime", "channel": 0, "simulated": False, "hardware_feedback": True, "feedback_required": True, "feedback_observed": True, "feedback_match": True, "physical_state": { "source": "physical_can", "channel": 0, "target": { "device": "seat", "zone": "front_right", "instance": "passenger", }, "position": { "longitudinal": { "position_pct": 63, "position_valid": True, } }, }, }, } verified = SelfExtensionService._authoritative_vehicle_feedback_state( operation ) self.assertIsNotNone(verified) self.assertEqual(verified["verification"]["channel"], 0) operation["telemetry"]["transport"] = "untrusted_bridge" self.assertIsNone( SelfExtensionService._authoritative_vehicle_feedback_state(operation) ) def test_ch0_bridge_report_exposes_verified_passenger_snapshot(self) -> None: function_config = { "metadata": {"request_id": "req-ch0-extension"}, "spec": { "operations": [ { "id": "passenger-longitudinal-63", "capability": "seat.set_longitudinal_position", } ] }, } bridge_result = { "result": "PASS", "details": { "summary": "物理反馈达到目标", "motors": [ { "position": 6300 if index == 0 else 5000, "position_valid": True, "active": False, } for index in range(4) ], "thermal": [0x01], }, } report = ZcanBridgeSeatExecutor._report( function_config, "2026-08-11T00:00:00Z", bridge_result, None, ) physical_state = report["spec"]["physical_state"] self.assertEqual(physical_state["source"], "physical_can") self.assertEqual(physical_state["channel"], 0) self.assertEqual( physical_state["position"]["longitudinal"]["position_pct"], 63 ) verified = SelfExtensionService._authoritative_vehicle_feedback_state( report["spec"]["results"][0] ) self.assertIsNotNone(verified) def test_vehicle_execution_ignores_browser_routing_overrides(self) -> None: with patch.dict( os.environ, { "COGNITIVE_AGENT_EXECUTION_MODE": "dry_run", "COGNITIVE_AGENT_PROVIDER": "local_rules", "COGNITIVE_AGENT_VEHICLE_ID": "denza_n7", }, clear=False, ): service = SelfExtensionService(SimulationGateway()) vehicle_service = Mock() vehicle_service.process.return_value = { "status": "succeeded", "execution_report": {"spec": {"results": [{"status": "succeeded"}]}}, } service.execute_vehicle( { "command": "seat driver vent low", "execution_mode": "hardware", "provider": "browser-selected-provider", "vehicle_id": "t2g_primary_seat", "simulation_fault": "adapter_disconnect", "channel": "0", }, vehicle_service, hardware_access_bypass=True, ) args = vehicle_service.process.call_args.args kwargs = vehicle_service.process.call_args.kwargs self.assertEqual(args[1], "local_rules") self.assertEqual(args[3], "dry_run") self.assertEqual(kwargs["vehicle_id"], "denza_n7") self.assertEqual(kwargs["simulation_fault"], "normal") def test_tcp_gateway_recovers_from_unavailable_address_and_tracks_peers(self) -> None: probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) probe.bind(("127.0.0.1", 0)) port = int(probe.getsockname()[1]) probe.close() class UnavailableAddressSocket: def setsockopt(self, *args: object) -> None: del args def bind(self, address: object) -> None: del address raise OSError(10049, "address is not available") def close(self) -> None: return socket_attempts = 0 def new_server_socket() -> object: nonlocal socket_attempts socket_attempts += 1 if socket_attempts == 1: return UnavailableAddressSocket() return socket.socket(socket.AF_INET, socket.SOCK_STREAM) clients: list[socket.socket] = [] with patch.dict( os.environ, { "GSCU_TCP_LISTEN_HOST": "127.0.0.1", "GSCU_TCP_PORT": str(port), "GSCU_TCP_BIND_RETRY_SECONDS": "0.05", }, ): gateway = TcpGateway() with patch.object(gateway, "_new_server_socket", side_effect=new_server_socket): gateway.start() try: self.assertTrue( self._wait_until(lambda: gateway.status()["listening"]), gateway.status(), ) self.assertGreaterEqual(socket_attempts, 2) waiting = gateway.status() self.assertFalse(waiting["connected"]) self.assertIn("等待设备接入", waiting["message"]) waiting_health = SelfExtensionService(gateway).health() self.assertTrue(waiting_health["listening"]) self.assertFalse(waiting_health["peer_connected"]) first = socket.create_connection(("127.0.0.1", port), timeout=1) clients.append(first) self.assertTrue( self._wait_until(lambda: gateway.status()["connected"]), gateway.status(), ) first_peer = gateway.status()["peer"] self.assertTrue(str(first_peer).startswith("127.0.0.1:")) connected_health = SelfExtensionService(gateway).health() self.assertTrue(connected_health["connected"]) self.assertEqual(connected_health["peer"], first_peer) # Replacing an old peer must not let its reader mark the new # GSCU1B session offline when the old socket closes. second = socket.create_connection(("127.0.0.1", port), timeout=1) clients.append(second) self.assertTrue( self._wait_until( lambda: gateway.status()["peer"] not in {None, first_peer} ), gateway.status(), ) time.sleep(0.1) self.assertTrue(gateway.status()["connected"]) second.close() clients.remove(second) self.assertTrue( self._wait_until(lambda: not gateway.status()["connected"]), gateway.status(), ) self.assertTrue(gateway.status()["listening"]) third = socket.create_connection(("127.0.0.1", port), timeout=1) clients.append(third) self.assertTrue( self._wait_until(lambda: gateway.status()["connected"]), gateway.status(), ) finally: for client in clients: client.close() gateway.close() stopped = gateway.status() self.assertFalse(stopped["listening"]) self.assertFalse(stopped["connected"]) def test_tcp_gateway_recovers_after_port_conflict_is_released(self) -> None: blocker = socket.socket(socket.AF_INET, socket.SOCK_STREAM) blocker.bind(("127.0.0.1", 0)) blocker.listen(1) port = int(blocker.getsockname()[1]) with patch.dict( os.environ, { "GSCU_TCP_LISTEN_HOST": "127.0.0.1", "GSCU_TCP_PORT": str(port), "GSCU_TCP_BIND_RETRY_SECONDS": "0.05", }, ): gateway = TcpGateway() gateway.start() try: self.assertTrue( self._wait_until(lambda: "监听失败" in gateway.status().get("message", "")), gateway.status(), ) self.assertFalse(gateway.status()["listening"]) blocker.close() self.assertTrue( self._wait_until(lambda: gateway.status()["listening"]), gateway.status(), ) self.assertNotIn("失败", gateway.status()["message"]) finally: blocker.close() gateway.close() def test_tcp_gateway_can_restart_immediately_after_close(self) -> None: probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) probe.bind(("127.0.0.1", 0)) port = int(probe.getsockname()[1]) probe.close() with patch.dict( os.environ, { "GSCU_TCP_LISTEN_HOST": "127.0.0.1", "GSCU_TCP_PORT": str(port), "GSCU_TCP_BIND_RETRY_SECONDS": "0.05", }, ): gateway = TcpGateway() try: gateway.start() self.assertTrue(self._wait_until(lambda: gateway.status()["listening"])) gateway.close() gateway.start() self.assertTrue( self._wait_until(lambda: gateway.status()["listening"]), gateway.status(), ) finally: gateway.close() def test_tcp_gateway_rejects_peer_accepted_during_close(self) -> None: settimeout_entered = threading.Event() allow_settimeout_to_finish = threading.Event() accepted_socket_closed = threading.Event() unexpected_reader_started = threading.Event() allow_unexpected_reader_to_finish = threading.Event() class AcceptedSocket: def settimeout(self, timeout: float) -> None: del timeout settimeout_entered.set() allow_settimeout_to_finish.wait(timeout=2) def setsockopt(self, *args: object) -> None: del args def recv(self, size: int) -> bytes: del size unexpected_reader_started.set() allow_unexpected_reader_to_finish.wait(timeout=2) return b"" def close(self) -> None: accepted_socket_closed.set() accepted_socket = AcceptedSocket() class ServerSocket: def setsockopt(self, *args: object) -> None: del args def bind(self, address: object) -> None: del address def listen(self, backlog: int) -> None: del backlog def settimeout(self, timeout: float) -> None: del timeout def accept(self) -> tuple[AcceptedSocket, tuple[str, int]]: return accepted_socket, ("127.0.0.1", 4444) def close(self) -> None: return with patch.dict( os.environ, { "GSCU_TCP_LISTEN_HOST": "127.0.0.1", "GSCU_TCP_PORT": "4444", "GSCU_TCP_BIND_RETRY_SECONDS": "0.05", }, ): gateway = TcpGateway() with patch.object(gateway, "_new_server_socket", return_value=ServerSocket()): gateway.start() self.assertTrue(settimeout_entered.wait(timeout=1)) close_thread = threading.Thread(target=gateway.close) close_thread.start() try: self.assertTrue(self._wait_until(gateway._stop.is_set)) finally: allow_settimeout_to_finish.set() close_thread.join(timeout=3) try: self.assertFalse(close_thread.is_alive()) self.assertTrue(accepted_socket_closed.is_set()) self.assertFalse(unexpected_reader_started.is_set()) self.assertFalse(gateway.status()["connected"]) self.assertIsNone(gateway.status()["peer"]) finally: allow_unexpected_reader_to_finish.set() if __name__ == "__main__": unittest.main()