def test_write_row_sanity_check(self): """A sanity check should be provided which disallows unknown columns.""" s = StringIO() t = TimingLogger(s, add_header=False) with pytest.raises(Exception): t._write_row(foo="bar")
def test_write_row_partial_defaults(self): """Provided columns should be filled out and others left with NAs.""" s = StringIO() t = TimingLogger(s, add_header=False) t._write_row(event_type="foo", num_attempts=123) assert s.getvalue() == "foo,{},123\n".format( # pragma: no branch ",".join("NA" for _ in range(len(self.expected_columns) - 2)))
def test_write_row_all(self): """If all values are provided, the order should be correct.""" s = StringIO() t = TimingLogger(s, add_header=False) t._write_row(**{c: i for i, c in enumerate(self.expected_columns)}) assert s.getvalue() == "{}\n".format( # pragma: no branch ",".join(str(i) for i in range(len(self.expected_columns))))
def test_write_row_defaults(self): """All columns should be filled with NAs by default.""" s = StringIO() t = TimingLogger(s, add_header=False) t._write_row() assert s.getvalue() == "{}\n".format( # pragma: no branch ",".join("NA" for _ in self.expected_columns))
def test_realtime(self): """Test that the _realtime function which is monkeypatched out in other tests would actually work if left. """ t = TimingLogger(StringIO()) rt = t._realtime() # Should contain a time assert re.search(r"[0-9]{2}:[0-9]{2}:[0-9]{2}", rt) # Should contain a date assert re.search(r"[0-9]{4}[-/][0-9]{2}[-/][0-9]{2}", rt)
def t(self, entries, time, monkeypatch): """Return a monkey-patched TimingLogger which uses the fake time module whose output is logged as a list of dictionaries in the 'entries' dictionary. """ class MockFile(object): """Decode and record the CSV data written.""" def __init__(self, entries): self.columns = None self.entries = entries def write(self, data): # Sanity check: make sure the line is well-formed assert data.endswith("\n") data = data.strip("\n") # Read out data values = data.split(",") if self.columns is None: # The first line read gives the column headings self.columns = values else: # Subsequent lines should be unpacked assert len(values) == len(self.columns) self.entries.append({c: v for v, c in zip(values, self.columns) if v != "NA"}) # Maintain the appearence of actually being a file... return len(data) # Make the _realtime just return NA to ease testing monkeypatch.setattr(TimingLogger, "_realtime", (lambda _: "<NOW>")) t = TimingLogger(MockFile(entries)) # Start logging t.logging_started() assert len(entries) == 1 assert entries.pop() == { "event_type": "logging_started", "time": "0.0", "realtime": "<NOW>", } return t
def main(args=None): parser = argparse.ArgumentParser( description= "Interactively guide the user through the process of wiring up a " "SpiNNaker machine.") arguments.add_version_args(parser) parser.add_argument("--no-tts", action="store_true", default=False, help="disable text-to-speech announcements of wiring " "steps") parser.add_argument("--no-auto-advance", action="store_true", default=False, help="disable auto-advancing through wiring steps") parser.add_argument("--fix", action="store_true", default=False, help="detect errors in existing wiring and just show " "corrective steps") parser.add_argument( "--log", type=str, metavar="LOGFILE", help="record the times at which each cable is installed") arguments.add_topology_args(parser) arguments.add_cabinet_args(parser) arguments.add_wire_length_args(parser) arguments.add_bmp_args(parser) arguments.add_proxy_args(parser) arguments.add_subset_args(parser) # Process command-line arguments args = parser.parse_args(args) (w, h), transformation, uncrinkle_direction, folds =\ arguments.get_topology_from_args(parser, args) cabinet, num_frames = arguments.get_cabinets_from_args(parser, args) wire_lengths, min_slack = arguments.get_wire_lengths_from_args( parser, args, mandatory=True) bmp_ips = arguments.get_bmps_from_args(parser, args, cabinet.num_cabinets, num_frames) proxy_host_port = arguments.get_proxy_from_args(parser, args) wire_filter = arguments.get_subset_from_args(parser, args) if cabinet.num_cabinets == num_frames == 1: num_boards = 3 * w * h else: num_boards = cabinet.boards_per_frame # Generate folded system hex_boards, folded_boards = folded_torus(w, h, transformation, uncrinkle_direction, folds) # Divide into cabinets cabinetised_boards = transforms.cabinetise(folded_boards, cabinet.num_cabinets, num_frames, cabinet.boards_per_frame) cabinetised_boards = transforms.remove_gaps(cabinetised_boards) physical_boards = transforms.cabinet_to_physical(cabinetised_boards, cabinet) # Focus on only the boards which are part of the system if cabinet.num_cabinets > 1: focus = [slice(0, cabinet.num_cabinets)] elif num_frames > 1: focus = [0, slice(0, num_frames)] else: focus = [0, 0, slice(0, w * h * 3)] # Generate wiring plan wires_between_boards, wires_between_frames, wires_between_cabinets =\ generate_wiring_plan(cabinetised_boards, physical_boards, cabinet.board_wire_offset, wire_lengths, min_slack) flat_wiring_plan = flatten_wiring_plan(wires_between_boards, wires_between_frames, wires_between_cabinets, cabinet.board_wire_offset) # Create a BMP connection/wiring probe or connect to a proxy if proxy_host_port is None: if len(bmp_ips) == 0: if args.fix: parser.error( "--fix requires that all BMPs be listed with --bmp") bmp_controller = None wiring_probe = None else: bmp_controller = BMPController(bmp_ips) # Create a wiring probe if bmp_controller is not None and (not args.no_auto_advance or args.fix): wiring_probe = WiringProbe(bmp_controller, cabinet.num_cabinets, num_frames, num_boards) else: # Fix is not supported since the proxy client does not recreate the # discover_wires method of WiringProbe. if args.fix: parser.error("--fix cannot be used with --proxy") # The proxy object provides a get_link_target and set_led method compatible # with those provided by bmp_controller and wiring_probe. Since these are # the only methods used, we use the proxy client object in place of # bmp_controller and wiring_probe. bmp_controller = wiring_probe = ProxyClient(*proxy_host_port) # Create a TimingLogger if required if args.log: if os.path.isfile(args.log): logfile = open(args.log, "a") add_header = False else: logfile = open(args.log, "w") add_header = True timing_logger = TimingLogger(logfile, add_header) else: logfile = None timing_logger = None # Convert wiring plan into cabinet coordinates b2c = dict(cabinetised_boards) wires = [] for ((src_board, src_direction), (dst_board, dst_direction), wire_length) \ in flat_wiring_plan: sc, sf, sb = b2c[src_board] dc, df, db = b2c[dst_board] wires.append(((sc, sf, sb, src_direction), (dc, df, db, dst_direction), wire_length)) # Filter wires according to user-specified rules wires = list(filter(wire_filter, wires)) if len(wires) == 0: parser.error("--subset selects no wires") if not args.fix: # If running normally, just run through the full set of wires wiring_plan = wires else: # If running in fix mode, generate a list of fixes to make correct_wires = set((src, dst) for src, dst, length in wires) actual_wires = set(wiring_probe.discover_wires()) to_remove = actual_wires - correct_wires to_add = correct_wires - actual_wires # Remove all bad wires first, then re-add good ones (note ordering now is # just reset to cabinets right-to-left, frames top-to-bottom and boards # left-to-right). wiring_plan = [(src, dst, None) for src, dst in sorted(to_remove)] for src, dst, length in wires: if (src, dst) in to_add: wiring_plan.append((src, dst, length)) if len(wiring_plan) == 0: print("No corrections required.") return 0 # Intialise the GUI and launch the mainloop ui = InteractiveWiringGuide(cabinet=cabinet, wire_lengths=wire_lengths, wires=wiring_plan, bmp_controller=bmp_controller, use_tts=not args.no_tts, focus=focus, wiring_probe=wiring_probe, auto_advance=not args.no_auto_advance, timing_logger=timing_logger) ui.mainloop() if logfile is not None: logfile.close() return 0
def test_no_header(self): """Headers should be suppressed when required.""" s = StringIO() t = TimingLogger(s, add_header=False) assert s.getvalue() == ""
def test_header(self): """Headers should be generated when required.""" s = StringIO() t = TimingLogger(s) assert s.getvalue() == "{}\n".format(",".join(self.expected_columns))