def setUp(self): packet = { "message": "N99 G2 8 x0 Y-0.2345 z +12.345 67 ab C(comment)*92; noise", "answer": "ok" } self.g = Gcode(packet) self.g.printer.unit_factor = 1.0
def test_gcode_parser(self): # Gocde::__init__ self.assertEqual(self.g.gcode, "G28") self.assertEqual(self.g.tokens, ["X0", "Y-0.2345", "Z+12.34567", "A", "B", "C"]) packet = {"message": "; noise"} g = Gcode(packet) self.assertEqual(g.code(), "No-Gcode")
def execute(self, g): self.printer.running_M116 = True # heater_index: # -1 - HBP, 0 - E, 1 - H, 2 - A, 3 - B, 4 - C # No P or H Parameter means all temperatures must be reached has_parameter = g.has_letter("P") or g.has_letter("T") if has_parameter: if g.has_letter("P"): # Set hotend temp based on the P-param heater_index = g.get_int_by_letter("P", 0) elif g.has_letter("T"): # Set hotend temp based on the T-param heater_index = g.get_int_by_letter("T", 0) if heater_index > len(self.printer.heaters) - 1: logging.warning("M116: heater index out of bounds: {}".format(heater_index)) return all_ok = [ has_parameter and heater_index != 0, has_parameter and heater_index != 1, has_parameter and heater_index != -1 ] if self.printer.config.reach_revision: all_ok.extend([ has_parameter and heater_index != 2, has_parameter and heater_index != 3, has_parameter and heater_index != 4 ]) while True: all_ok[0] |= self.printer.heaters['E'].is_target_temperature_reached() all_ok[1] |= self.printer.heaters['H'].is_target_temperature_reached() all_ok[2] |= self.printer.heaters['HBP'].is_target_temperature_reached() if self.printer.config.reach_revision: all_ok[3] |= self.printer.heaters['A'].is_target_temperature_reached() all_ok[4] |= self.printer.heaters['B'].is_target_temperature_reached() all_ok[5] |= self.printer.heaters['C'].is_target_temperature_reached() m105 = Gcode({"message": "M105", "parent": g}) self.printer.processor.resolve(m105) self.printer.processor.execute(m105) if False not in all_ok or not self.printer.running_M116: logging.info("Heating done.") self.printer.send_message(g.prot, "Heating done.") self.printer.running_M116 = False return else: answer = m105.get_answer() answer += " E: " + \ ("0" if self.printer.current_tool == "E" else "1") m105.set_answer(answer[2:]) # strip away the "ok" self.printer.reply(m105) time.sleep(1)
def test_gcodes_M105(self): test_temps = {} test_target_temps = {} test_powers = {} for n in self.printer.heaters: prefix = self.printer.heaters[n].prefix test_temps[prefix] = round(random() * 100, 2) test_target_temps[prefix] = round(random() * 100, 2) test_powers[prefix] = round(random(), 2) self.printer.heaters[n].get_temperature = mock.Mock( return_value=test_temps[prefix]) self.printer.heaters[n].get_target_temperature = mock.Mock( return_value=test_target_temps[prefix]) self.printer.heaters[n].mosfet = mock.Mock( ) # TODO why is this necessary? All the heaters seem to be sharing the same mosfet? self.printer.heaters[n].mosfet.get_power = mock.Mock( return_value=test_powers[prefix]) g = Gcode({"message": "M105"}) self.printer.processor.gcodes[g.gcode].execute(g) expected_answer = "ok" expected_answer += " {0}:{1:.1f}/{2:.1f}".format( "T", test_temps["T0"], test_target_temps["T0"]) for heater, data in sorted(iteritems(self.printer.heaters), key=lambda (k, v): (v, k)): p = data.prefix expected_answer += " {0}:{1:.1f}/{2:.1f}".format( p, test_temps[p], test_target_temps[p]) expected_answer += " @:{0:.1f}".format( math.floor(test_powers["T0"] * 255.0)) self.assertEqual(g.answer, expected_answer)
def test_gcodes_M114(self): A = round(random() * 200, 1) B = round(random() * 200, 1) C = round(random() * 200, 1) X = round(random() * 200, 1) Y = round(random() * 200, 1) Z = round(random() * 200, 1) E = round(random() * 200, 1) H = round(random() * 200, 1) self.printer.path_planner.get_current_pos = mock.Mock(return_value={ 'A': A, 'C': C, 'B': B, 'E': E, 'H': H, 'Y': Y, 'X': X, 'Z': Z }) g = Gcode({"message": "M114"}) self.printer.processor.gcodes[g.gcode].execute(g) self.printer.path_planner.get_current_pos.assert_called_with( ideal=True, mm=True) # kinda redundant, but hey. self.assertEqual( g.answer, "ok C: X:{:.1f} Y:{:.1f} Z:{:.1f} E:{:.1f} A:{:.1f} B:{:.1f} C:{:.1f} H:{:.1f}".format( X, Y, Z, E, A, B, C, H))
def execute(self, g): gcodes = self.printer.config.get("Macros", "G29").split("\n") self.printer.path_planner.wait_until_done() for gcode in gcodes: # If 'S' (imulate) remove M561 and M500 codes if (g.has_letter("S")) and ("RFS" in gcode): logging.debug("G29: Removing due to RFS: " + str(gcode)) else: # Execute the code G = Gcode({"message": gcode, "parent": g}) self.printer.processor.resolve(G) self.printer.processor.execute(G) self.printer.path_planner.wait_until_done() probe_data = copy.deepcopy(self.printer.probe_points) bed_data = { "probe_data": { "x": [], "y": [], "z": [] }, "probe_type": "test" if g.has_letter("S") else "probe", "replicape_key": self.printer.config.replicape_key } for k, v in enumerate(probe_data): bed_data["probe_data"]["x"].append(probe_data[k]["X"]) bed_data["probe_data"]["y"].append(probe_data[k]["Y"]) bed_data["probe_data"]["z"].append(self.printer.probe_heights[k]) Alarm.action_command("bed_probe_data", json.dumps(bed_data))
def assertGcodeProperties(self, gcode, is_buffered=False, is_async=False): gcode_instance = Gcode({"message": gcode}) self.printer.processor.resolve(gcode_instance) gcode_handler = gcode_instance.command self.assertNotEqual(gcode_handler.get_description(), "") self.assertNotEqual(gcode_handler.get_long_description(), "") self.assertEqual(gcode_handler.is_buffered(), is_buffered) self.assertEqual(gcode_handler.is_async(), is_async)
def execute(self, g): gcodes = self.printer.config.get("Macros", "G31").split("\n") self.printer.path_planner.wait_until_done() for gcode in gcodes: G = Gcode({"message": gcode, "parent": g}) self.printer.processor.resolve(G) self.printer.processor.execute(G) self.printer.path_planner.wait_until_done()
def execute(self, g): temperature = g.get_float_by_letter("S") self.printer.heaters['HBP'].set_target_temperature(temperature) G = Gcode({ "message": "M116 P-1", # P-1 = HBP "parent": g }) self.printer.processor.resolve(G) self.printer.processor.execute(G)
def test_code(test_str): r = Redeem("/usr/src/redeem/configs") r.printer.enable.set_enabled() g = r.printer.processor for line in test_str.splitlines(): if line: print line g.execute(Gcode({"message": line, "prot": "testing"})) r.exit()
def execute(self, g): m104 = Gcode({ "message": "M104 " + " ".join(g.get_tokens()), "parent": g }) self.printer.processor.resolve(m104) self.printer.processor.execute(m104) has_parameter = g.has_letter("P") or g.has_letter("T") if not has_parameter: heaters = ["E", "H"] if self.printer.config.reach_revision: heaters.extend(["A", "B", "C"]) parameters = ["P" + str(heaters.index(self.printer.current_tool))] else: parameters = g.get_tokens() m116 = Gcode({"message": "M116 " + " ".join(parameters), "parent": g}) self.printer.processor.resolve(m116) self.printer.processor.execute(m116)
def test_file(test): r = Redeem("/usr/src/redeem/configs") r.printer.enable.set_enabled() g = r.printer.processor f = open(test, 'r') for line in f.readlines(): if line: print line g.execute(Gcode({"message": line, "prot": "testing"})) r.exit() f.close()
def test4(): test_str = """ G28 G1 F5000.000 G1 Z0.350 G1 X-100.0 Y-100.0 G1 X100.0 Y-100.0 G1 X100.0 Y100.0 G1 X-100.0 Y100.0 G1 X0.0 Y0.0 """ r = Redeem() g = r.printer.processor for line in test_str.splitlines(): if line: print line g.execute(Gcode({"message": line, "prot": "testing"})) r.exit()
def test_gcodes_M115(self): g = Gcode({"message": "M115"}) self.printer.processor.gcodes[g.gcode].execute(g) self.assertRegexpMatches(g.answer, "PROTOCOL_VERSION:\S+") self.assertRegexpMatches(g.answer, "REPLICAPE_KEY:TESTING_DUMMY_KEY") self.assertRegexpMatches(g.answer, "FIRMWARE_NAME:Redeem") self.assertRegexpMatches( g.answer, "FIRMWARE_VERSION:{}\s".format(re.escape(__long_version__))) self.assertRegexpMatches(g.answer, "FIRMWARE_URL:https:\S+") self.assertRegexpMatches( g.answer, "MACHINE_TYPE:{}\s".format( re.escape(self.printer.config.get('System', 'machine_type')))) self.assertRegexpMatches( g.answer, "EXTRUDER_COUNT:{}".format(self.printer.NUM_EXTRUDERS)) self.assertRegexpMatches( g.answer, "DISTRIBUTION_NAME:halloween DISTRIBUTION_VERSION:bumpkins")
def process_gcode(self, g): profile = cProfile.Profile() self.printer.sd_card_manager.set_status(True) profile.enable() for line in self.printer.sd_card_manager: line = line.strip() if not line or line.startswith(';'): continue file_g = Gcode({"message": line}) self.printer.processor.enqueue(file_g) if self.printer.sd_card_manager.get_status(): logging.info("M24: Print from file complete") self.printer.sd_card_manager.set_status(False) self.printer.send_message(g.prot, "Done printing file") profile.disable() s = StringIO.StringIO() sortby = 'cumulative' ps = pstats.Stats(profile, stream=s).sort_stats(sortby) ps.print_stats() logging.debug(s.getvalue()) self.printer.sd_card_manager.reset()
def test_gcode_is_info_command(self): self.assertEqual(self.g.is_info_command(), False) g = Gcode({"message": "G28?"}) self.assertEqual(g.is_info_command(), True)
def exec_and_wait(cmd): G = Gcode({"message": cmd, "prot": g.prot}) self.printer.processor.execute(G) self.printer.path_planner.wait_until_done()
def execute(self, g): # Catch most letters to tell user use may have been in error. if g.has_letter("P"): self.printer.send_message( g.prot, "Warning: P not supported for G30.1, proceeding as if none existed.") if g.has_letter("X"): # Override X self.printer.send_message( g.prot, "Warning: X not supported for G30.1, proceeding as if none existed.") if g.has_letter("Y"): # Override Y self.printer.send_message( g.prot, "Warning: Y not supported for G30.1, proceeding as if none existed.") if g.has_letter("Z"): # Override Z Z_new = g.get_float_by_letter("Z") else: Z_new = 0 # Usable letters listed here # Get probe length, if present, else use value from config. if g.has_letter("D"): probe_length = g.get_float_by_letter("D") / 1000. else: probe_length = self.printer.config.getfloat('Probe', 'length') # Get probe speed. If not preset, use printer's current speed. if g.has_letter("F"): probe_speed = g.get_float_by_letter("F") / 60000.0 else: probe_speed = self.printer.config.getfloat('Probe', 'speed') # Get acceleration. If not present, use value from config. if g.has_letter("Q"): probe_accel = g.get_float_by_letter("Q") / 3600000.0 else: probe_accel = self.printer.config.getfloat('Probe', 'accel') point = self.printer.path_planner.get_current_pos(mm=True, ideal=True) logging.debug("G30.1: current position (mm) : X{} Y{} Z{}".format( point["X"], point["Y"], point["Z"])) logging.debug("G30.1: will set bed to Z{}".format(Z_new)) # Find the Probe offset # values in config file are in metres, need to convert to millimetres offset_x = self.printer.config.getfloat('Probe', 'offset_x') * 1000 offset_y = self.printer.config.getfloat('Probe', 'offset_y') * 1000 offset_z = self.printer.config.getfloat('Probe', 'offset_z') * 1000 logging.debug("G30.1: probing from point (mm) : X{} Y{} Z{}".format( point["X"] + offset_x, point["Y"] + offset_y, point["Z"])) # Move to the position G0 = Gcode({ "message": "G0 X{} Y{} Z{}".format(point["X"] + offset_x, point["Y"] + offset_y, point["Z"]), "parent": g }) self.printer.processor.resolve(G0) self.printer.processor.execute(G0) self.printer.path_planner.wait_until_done() bed_dist = self.printer.path_planner.probe(probe_length, probe_speed, probe_accel) * 1000.0 # convert to mm logging.info("G30: adjusting offset using Z-probe offset by " + str(offset_z)) bed_dist = bed_dist - offset_z # apply z offset # calculated required offset to make bed equal to Z0 or user's specified Z. # should be correct, assuming probe starts at Z(5), requested Z(0) probe Z(-0.3), adjusted global Z should be 5.3 # assuming probe starts at Z(5), and probe result is -0.3. This says real probe start was 5.3. # if we want to start printing at 0.2 above bed, new z should be 5.1. # 5 - (-0.3) - 0.2 = 5.1 Z_adjusted = point["Z"] - bed_dist - Z_new logging.debug("Bed dist: " + str(bed_dist) + " mm") logging.debug("Old Z{}, New Z{}.".format(point["Z"], Z_adjusted)) self.printer.send_message(g.prot, "Offsetting Z by {} to Z{}".format(bed_dist, Z_adjusted)) # wiley's advice on bypassing sending via G92 pos = {} pos["Z"] = Z_adjusted / 1000.0 path = G92Path(pos, self.printer.feed_rate) self.printer.path_planner.add_path(path) # not sure if next part is necessary Alarm.action_command("bed_probe_point", json.dumps([point["X"], point["Y"], bed_dist]))
def test_gcode_set_tokens(self): g = Gcode({"message": "G28"}) g.set_tokens(["G28", "X0", "Y0"]) self.assertEqual(g.tokens, ["G28", "X0", "Y0"])
def test_gcode_is_valid(self): self.assertEqual(self.g.is_valid(), True) packet = {"message": "N99 G28*60; noise"} # invalid checksum g = Gcode(packet) self.assertEqual(g.is_valid(), False)
def execute(self, g): if g.has_letter("P"): # Load point index = g.get_int_by_letter("P") try: point = self.printer.probe_points[index] except IndexError: logging.warning("G30 point P%d not yet defined. Aborting.", index) return else: # If no probe point is specified, use current position # this value is in metres # convert to millimetres as we are using this value for a G0 call point = self.printer.path_planner.get_current_pos(mm=True, ideal=True) logging.debug("G30: current position (mm) : X{} Y{} Z{}".format( point["X"], point["Y"], point["Z"])) """ do not convert to SI m because used as is in gcode command, below """ if g.has_letter("X"): # Override X point["X"] = g.get_float_by_letter("X") if g.has_letter("Y"): # Override Y point["Y"] = g.get_float_by_letter("Y") if g.has_letter("Z"): # Override Z point["Z"] = g.get_float_by_letter("Z") # Get probe length, if present, else use value from config. if g.has_letter("D"): probe_length = g.get_float_by_letter("D") / 1000. else: probe_length = self.printer.config.getfloat('Probe', 'length') # Get probe speed, if present, else use value from config. if g.has_letter("F"): probe_speed = g.get_float_by_letter("F") / 60000. # m/s else: probe_speed = self.printer.config.getfloat('Probe', 'speed') # Get acceleration, if present, else use value from config. if g.has_letter("Q"): probe_accel = g.get_float_by_letter("Q") / 3600000. # m/s^2 else: probe_accel = self.printer.config.getfloat('Probe', 'accel') # Find the Probe offset # values in config file are in metres, need to convert to millimetres offset_x = self.printer.config.getfloat('Probe', 'offset_x') * 1000 offset_y = self.printer.config.getfloat('Probe', 'offset_y') * 1000 offset_z = self.printer.config.getfloat('Probe', 'offset_z') * 1000 logging.debug("G30: probing from point (mm) : X{} Y{} Z{}".format( point["X"] + offset_x, point["Y"] + offset_y, point["Z"])) # Move to the position G0 = Gcode({ "message": "G0 X{} Y{} Z{}".format(point["X"] + offset_x, point["Y"] + offset_y, point["Z"]), "parent": g }) self.printer.processor.resolve(G0) self.printer.processor.execute(G0) self.printer.path_planner.wait_until_done() bed_dist = self.printer.path_planner.probe(probe_length, probe_speed, probe_accel) * 1000.0 # convert to mm logging.info("G30: adjusting offset using Z-probe offset by " + str(offset_z)) bed_dist = bed_dist - offset_z # apply z offset bed_dist = round(bed_dist, 3) logging.debug("Bed dist: " + str(bed_dist) + " mm") self.printer.send_message( g.prot, "Found Z probe distance {0:.2f} mm at (X, Y) = ({1:.2f}, {2:.2f})".format( bed_dist, point["X"], point["Y"])) Alarm.action_command("bed_probe_point", json.dumps([point["X"], point["Y"], bed_dist])) # Must have S to save the probe bed distance # this is required for calculation of the bed compensation matrix # NOTE: the use of S in G30 means "save". if g.has_letter("S"): if not g.has_letter("P"): logging.warning("G30: S-parameter was set, but no index (P) was set.") else: self.printer.probe_heights[index] = bed_dist
def test2(): r = Redeem() g = r.printer.processor g.execute(Gcode({"message": "G28", "prot": "testing"})) r.exit()
def execute(self, g): num_factors = g.get_int_by_letter("N", 4) if num_factors not in [3, 4, 6, 8, 9]: msg = "G33: Invalid number of calibration factors." logging.error(msg) self.printer.send_message(g.prot, msg) return # we reuse the G29 macro for the autocalibration purposes gcodes = self.printer.config.get("Macros", "G29").split("\n") self.printer.path_planner.wait_until_done() for gcode in gcodes: G = Gcode({"message": gcode, "parent": g}) self.printer.processor.resolve(G) self.printer.processor.execute(G) self.printer.path_planner.wait_until_done() # adjust probe heights print_head_zs = np.array( self.printer.probe_heights[:len(self.printer.probe_points)]) # Log the found heights logging.info("G33: Found heights: " + str(np.round(print_head_zs, 2))) simulate_only = g.has_letter("S") # run the actual delta autocalibration params = self.printer.path_planner.autocalibrate_delta_printer( num_factors, simulate_only, self.printer.probe_points, print_head_zs) logging.info("G33: Finished printer autocalibration\n") if g.has_letter("P"): # dump the dictionary to log file logging.debug(str(params)) # pretty print to printer output self.printer.send_message( g.prot, "delta calibration : L = %g" % params["L"]) self.printer.send_message( g.prot, "delta calibration : r = %g" % params["r"]) self.printer.send_message( g.prot, "delta calibration : A_angular = %g" % params["A_angular"]) self.printer.send_message( g.prot, "delta calibration : B_angular = %g" % params["B_angular"]) self.printer.send_message( g.prot, "delta calibration : C_angular = %g" % params["C_angular"]) self.printer.send_message( g.prot, "delta calibration : A_radial = %g" % params["A_radial"]) self.printer.send_message( g.prot, "delta calibration : B_radial = %g" % params["B_radial"]) self.printer.send_message( g.prot, "delta calibration : C_radial = %g" % params["C_radial"]) self.printer.send_message( g.prot, "delta calibration : offset_x = %g" % params["offset_x"]) self.printer.send_message( g.prot, "delta calibration : offset_y = %g" % params["offset_y"]) self.printer.send_message( g.prot, "delta calibration : offset_z = %g" % params["offset_z"]) return
def execute_gcode(cls, text): g = Gcode({"message": text}) g.prot = 'testing_noret' cls.printer.processor.resolve(g) g.command.execute(g) return g
def test_gcode_is_crc(self): self.assertEqual(self.g.is_crc(), True) g = Gcode({"message": "G28"}) self.assertEqual(g.is_crc(), False)
class GcodeTest(MockPrinter): def setUp(self): packet = { "message": "N99 G2 8 x0 Y-0.2345 z +12.345 67 ab C(comment)*92; noise", "answer": "ok" } self.g = Gcode(packet) self.g.printer.unit_factor = 1.0 def tearDown(self): self.g = None return def test_gcode_parser(self): # Gocde::__init__ self.assertEqual(self.g.gcode, "G28") self.assertEqual(self.g.tokens, ["X0", "Y-0.2345", "Z+12.34567", "A", "B", "C"]) packet = {"message": "; noise"} g = Gcode(packet) self.assertEqual(g.code(), "No-Gcode") def test_gcode_code(self): self.assertEqual(self.g.code(), "G28") def test_gcode_is_valid(self): self.assertEqual(self.g.is_valid(), True) packet = {"message": "N99 G28*60; noise"} # invalid checksum g = Gcode(packet) self.assertEqual(g.is_valid(), False) def test_gcode_token_letter(self): self.assertEqual(self.g.token_letter(2), "Z") def test_token_value(self): self.assertEqual(self.g.token_value(1), -0.2345) def test_gcode_token_distance(self): self.assertEqual(self.g.token_distance(2), 12.34567) self.g.printer.unit_factor = 2.54 self.assertEqual(self.g.token_distance(2), 12.34567 * 2.54) def test_gcode_get_tokens(self): self.assertEqual(self.g.get_tokens(), self.g.tokens) def test_gcode_set_tokens(self): g = Gcode({"message": "G28"}) g.set_tokens(["G28", "X0", "Y0"]) self.assertEqual(g.tokens, ["G28", "X0", "Y0"]) def test_gcode_get_message(self): """ should return message with line number, checksum, gcode (comments) and non-standrd ;comments removed """ self.assertEqual(self.g.get_message(), "G2 8 x0 Y-0.2345 z +12.345 67 ab C") def test_gcode_has_letter(self): self.assertEqual(self.g.has_letter("Y"), True) self.assertEqual(self.g.has_letter("W"), False) self.assertEqual(self.g.has_letter("N"), False) self.assertEqual(self.g.has_letter("W"), False) self.assertEqual(self.g.has_letter("B"), True) self.assertEqual(self.g.has_letter("C"), True) def test_gcode_has_value(self): self.assertEqual(self.g.has_value(0), True) self.assertEqual(self.g.has_value(1), True) self.assertEqual(self.g.has_value(2), True) self.assertEqual(self.g.has_value(3), False) self.assertEqual(self.g.has_value(4), False) self.assertEqual(self.g.has_value(5), False) def test_gcode_get_token_index_by_letter(self): self.assertEqual(self.g.get_token_index_by_letter("X"), 0) self.assertEqual(self.g.get_token_index_by_letter("Y"), 1) self.assertEqual(self.g.get_token_index_by_letter("C"), 5) def test_gcode_get_float_by_letter(self): self.assertEqual(self.g.get_float_by_letter("Y"), -0.2345) def test_gcode_get_distance_by_letter(self): self.g.printer.unit_factor = 2.54 self.assertEqual(self.g.get_distance_by_letter("Y"), -0.2345 * 2.54) def test_gcode_get_int_by_letter(self): self.assertEqual(self.g.get_int_by_letter("Z"), 12) def test_gcode_has_letter_value(self): self.assertEqual(self.g.has_letter_value("X"), True) self.assertEqual(self.g.has_letter_value("A"), False) def test_gcode_remove_token_by_letter(self): self.g.remove_token_by_letter("Z") self.assertEqual(self.g.tokens, ["X0", "Y-0.2345", "A", "B", "C"]) def test_gcode_num_tokens(self): self.assertEqual(self.g.num_tokens(), 6) def test_gcode_get_tokens_as_dict(self): t = self.g.get_tokens_as_dict() keys = ["A", "B", "C", "X", "Y", "Z"] vals = [0.0, 0.0, 0.0, 0.0, -0.2345, 12.34567] for i, key in enumerate(sorted(t)): self.assertEqual(key, keys[i]) self.assertEqual(t[key], vals[i]) def test_gcode_get_cs(self): self.assertEqual(1, self.g._getCS("1234567890")) def test_gcode_is_crc(self): self.assertEqual(self.g.is_crc(), True) g = Gcode({"message": "G28"}) self.assertEqual(g.is_crc(), False) def test_gcode_get_answer(self): self.assertEqual(self.g.get_answer(), "ok") def test_gcode_set_answer(self): self.assertEqual(self.g.answer, "ok") self.g.set_answer("xxx") self.assertEqual(self.g.answer, "xxx") def test_gcode_is_info_command(self): self.assertEqual(self.g.is_info_command(), False) g = Gcode({"message": "G28?"}) self.assertEqual(g.is_info_command(), True)