def test_parameterItems_whitespace_labels_and_values(self):
        """Test that parameterItems permits whitespace between labels and values."""
        unit = GcodeParser()
        self.assertIsNone(unit.parameters, "The parameters should be None.")

        result = [item for item in unit.parameterItems("A  1 B C  2")]

        self.assertEqual(
            result, [("A", 1), ("B", None), ("C", 2), ("", "B C  2")],
            "The expected result should be generated."
        )
    def test_stringify_lineNumberFalse_checksumFalse(self):
        """Test stringify when includeLineNumber=False and includeChecksum=False."""
        unit = GcodeParser()
        unit.parse("N123   G28")

        result = unit.stringify(includeLineNumber=False, includeChecksum=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_parameterItems_nonAlphaFirstChar(self):
        """Test parameterItems when the first character is non-alphabetic."""
        unit = GcodeParser()

        self.assertIsNone(unit.parameters, "The parameters should be None.")

        result = [item for item in unit.parameterItems("12 A2")]

        self.assertEqual(
            result, [("A", 2), ("", "12 A2")],
            "The expected result should be generated."
        )
    def test_parameterItems_custom_string(self):
        """Test that parameterItems parses the given string when one is given."""
        unit = GcodeParser()

        self.assertIsNone(unit.parameters, "The parameters should be None.")

        result = [item for item in unit.parameterItems("A1BC2")]

        self.assertEqual(
            result, [("A", 1), ("B", None), ("C", 2), ("", "BC2")],
            "The expected result should be generated."
        )
    def test_parseLines_multipleLines(self):
        """Test parseLines when passed a string containing multiple lines."""
        unit = GcodeParser()

        result = []
        for item in unit.parseLines("command 1\rcommand 2\rcommand 3"):
            result.append(item.fullText)

        self.assertEqual(
            result, ["command 1\r", "command 2\r", "command 3"],
            "The expected lines should be parsed"
        )
    def test_stringify_defaults(self):
        """Test stringify when no parameters are supplied."""
        unit = GcodeParser()

        unit.parse("   N0123   G028  X  *107   ; Comment   \r\n")

        result = unit.stringify()

        self.assertEqual(
            result, "   N123 G28 X *75 ; Comment   \r\n",
            "The result should be the normalized text"
        )
    def test_parseLines_offset(self):
        """Test parseLines when passed an offset value."""
        unit = GcodeParser()

        result = []
        for item in unit.parseLines("command 1\rcommand 2\rcommand 3", 10):
            result.append(item.fullText)

        self.assertEqual(
            result, ["command 2\r", "command 3"],
            "The expected lines should be parsed"
        )
    def test_stringify_includeEolFalse(self):
        """Test stringify when includeEol=False."""
        unit = GcodeParser()

        unit.parse("G 28\n")
        self.assertEqual(unit.eol, "\n", "The eol should be '\\n'")

        result = unit.stringify(includeEol=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_includeCommentFalse_withComment(self):
        """Test stringify when includeComment=False and there is a comment."""
        unit = GcodeParser()

        unit.parse("G 28   ; My comment")
        self.assertIsNotNone(unit.comment, "The comment property should not be None")

        result = unit.stringify(includeComment=False)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_includeCommentTrue_commentNone(self):
        """Test stringify when includeComment=True and there is no comment."""
        unit = GcodeParser()

        unit.parse("G 28")
        self.assertIsNone(unit.comment, "The comment property should be None")

        result = unit.stringify(includeComment=True)

        self.assertEqual(
            result, "G28",
            "The expected result should be returned"
        )
    def test_stringify_parametersNotNone(self):
        """Test stringify when the parameters property is not None."""
        unit = GcodeParser()

        unit.parse("G 28 X  Y")
        self.assertIsNotNone(unit.parameters, "The parameters property should not be None")

        result = unit.stringify()

        self.assertEqual(
            result, "G28 X  Y",
            "The expected result should be returned"
        )
    def test_stringify_subCodeNotNone(self):
        """Test stringify when the subCode is not None."""
        unit = GcodeParser()

        unit.parse("G 28.1")
        self.assertIsNotNone(unit.subCode, "The subCode property should not be None")

        result = unit.stringify()

        self.assertEqual(
            result, "G28.1",
            "The expected result should be returned"
        )
    def test_parameterDict_setter_emptyStr_none(self):
        """Test the parameterDict setter when passed {"": None}."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"": None}

        self.assertIsNone(unit.parameters, "The parameters should be None")
        self.assertIsNone(unit.parameterDict, "The parameterDict should be None")
    def test_parameterItems_no_string_given(self):
        """Test that parameterItems uses the parameters property when no string is given."""
        unit = GcodeParser()
        unit.parse("G1 A1BC2")

        self.assertEqual(unit.parameters, "A1BC2", "The parameters should be 'A1BC2'.")

        result = [item for item in unit.parameterItems()]

        self.assertEqual(
            result, [("A", 1), ("B", None), ("C", 2), ("", "BC2")],
            "The expected result should be generated."
        )
    def test_str(self):
        """Test the __str__ magic method."""
        unit = GcodeParser()

        with mock.patch.object(unit, 'stringify') as mockStringify:
            mockStringify.return_value = "expectedResult"

            result = str(unit)

            mockStringify.assert_called_with()
            self.assertEqual(
                result, "expectedResult",
                "The result of stringify should be returned."
            )
    def test_commandString_notCached(self):
        """Test the commandString property when no value is cached."""
        unit = GcodeParser()
        unit._commandString = None  # pylint: disable=protected-access

        with mock.patch.object(unit, 'stringify') as mockStringify:
            mockStringify.return_value = "foo bar"

            result = unit.commandString

            mockStringify.assert_called_with(
                includeChecksum=False,
                includeComment=False,
                includeEol=False
            )
            self.assertEqual(result, "foo bar", "The result of stringify should be returned.")
    def test_parameterDict_setter_label_value(self):
        """Test the parameterDict setter when passed a label and non empty value."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"A": 12}

        self.assertEqual(unit.parameters, "A12", "The parameters should be 'A12'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("A", 12)]),
            "The parameterDict should be the expected value"
        )
    def test_parameterDict_setter_label_none(self):
        """Test the parameterDict setter when passed a label with a value of None."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"A": None}

        self.assertEqual(unit.parameters, "A", "The parameters should be 'A'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("A", None), ("", "A")]),
            "The parameterDict should be the expected value"
        )
    def test_parameterDict_setter_noLabel_falsyValue(self):
        """Test the parameterDict setter when passed {"": "0"}."""
        unit = GcodeParser()
        unit.parse("G28 X")
        self.assertEqual(
            unit.parameterDict, OrderedDict([("X", None), ("", "X")]),
            "The parameterDict should be the expected value"
        )

        unit.parameterDict = {"": "0"}

        self.assertEqual(unit.parameters, "0", "The parameters should be '0'")
        self.assertEqual(
            unit.parameterDict,
            OrderedDict([("", "0")]),
            "The parameterDict should be the expected value"
        )
    def test_parameterDict_getter_cached(self):
        """Test the parameterDict getter when the result is already cached."""
        expected = OrderedDict([
            ("A", 1),
            ("B", 2)
        ])

        unit = GcodeParser()
        unit._parameters = "A1 B2"      # pylint: disable=protected-access
        unit._parameterDict = expected  # pylint: disable=protected-access

        with mock.patch.object(unit, 'parameterItems') as mockParameterItems:
            result = unit.parameterDict

            mockParameterItems.assert_not_called()
            self.assertEqual(
                result, expected,
                "The expected result should be returned"
            )
    def test_gcode_setter_whitespace(self):
        """Test the gcode setter when the supplied value has leading/trailing whitespace."""
        unit = GcodeParser()

        unit.gcode = "  G  1  "

        self.assertEqual(unit.type, "G", "The type should be 'G'.")
        self.assertEqual(unit.code, 1, "The code should be 1.")
        self.assertIsNone(unit.subCode, "The subCode should be None.")

        unit.gcode = "  M  17.1  "

        self.assertEqual(unit.type, "M", "The type should be 'M'.")
        self.assertEqual(unit.code, 17, "The code should be 17.")
        self.assertEqual(unit.subCode, 1, "The subCode should be 1.")

        unit.gcode = "  T  2  "

        self.assertEqual(unit.type, "T", "The type should be 'T'.")
        self.assertEqual(unit.code, 2, "The code should be 2.")
        self.assertEqual(unit.gcode, "T2", "The gcode should be 'T2'.")
    def test_parse_multiple_lines_last(self):
        """Test parse to parse the last of multiple lines."""
        unit = GcodeParser()
        source = (
            "G28 ;Home\n" +
            "G1 X1 Y2\n" +
            "G20"
        )
        unit.parse(source)  # Read the first line
        unit.parse()        # Read the second line

        result = unit.parse()

        self.assertEqual(result, unit, "The instance should be returned")
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=19,
            length=3,
            text="G20",
            type="G",
            code=20
        )
    def test_parse_multiple_lines_first(self):
        """Test parse to parse the first of multiple lines."""
        unit = GcodeParser()
        source = (
            "G28 ;Home\n" +
            "G1 X1 Y2\n" +
            "G20"
        )

        result = unit.parse(source)

        self.assertEqual(result, unit, "The instance should be returned")
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=0,
            length=10,
            text="G28 ",
            type="G",
            code=28,
            comment=";Home",
            eol="\n"
        )
    def test_computeChecksum(self):
        """Test computeChecksum."""
        unit = GcodeParser()

        self.assertEqual(
            unit.computeChecksum(""), 0,
            "computeChecksum should return 0 for an empty string"
        )

        self.assertEqual(
            unit.computeChecksum("A"), 65,
            "computeChecksum should return 65 for 'A'"
        )

        self.assertEqual(
            unit.computeChecksum("ABC"), 64,
            "computeChecksum should return 64 for 'ABC'"
        )

        self.assertEqual(
            unit.computeChecksum("foo bar"), 55,
            "computeChecksum should return 55 for 'foo bar'"
        )
    def _test_parse_line(  # pylint: disable=too-many-arguments, too-many-locals
            self,
            source,
            offset=0,
            length=None,
            leadingWhitespace="",
            text="",
            trailingWhitespace="",
            lineNumber=None,
            type=None,  # pylint: disable=redefined-builtin
            code=None,
            subCode=None,
            parameters=None,
            parameterDict=None,
            rawChecksum=None,
            checksum=None,
            comment=None,
            eol=""
    ):
        """
        Test the parse method given a source string/offset and expected length and property values.

        Assertions will be made to verify the resulting values of the following properties, based
        on the parameter values provided:
            source, offset, length, leadingWhitespace, text, trailingWhitespace, lineNumber, type,
            code, gcode (based on the provided type and code), subCode, parameters, rawChecksum,
            checksum, comment, eol, and fullText (based on the provided string, offset and length).

        Parameters
        ----------
        source : string
            The source string to attempt to parse.
        offset : int
            The offset within the source to start parsing the line.
        length : int | None
            The expected length of the match.  If None, the entire source string will be expected
            to match.
        leadingWhitespace : string
            The expected parsed 'leadingWhitespace' value.
        text : string
            The expected parsed 'text' value.
        trailingWhitespace : string
            The expected parsed 'trailingWhitespace' value.
        lineNumber : int | None
            The expected parsed 'lineNumber' value.
        type : string | None
            The expected parsed 'type' value.
        code : int | None
            The expected parsed 'code' value.
        subCode : int | None
            The expected parsed 'subCode' value.
        parameters : string | None
            The expected parsed 'parameters' value.
        parameterDict : dict | None
            The expected parsed parameter labels and values.
        rawChecksum : string | None
            The expected parsed 'rawChecksum' value.
        checksum : int | None
            The expected parsed 'checksum' value.
        comment : string | None
            The expected parsed 'comment' value.
        eol : string
            The expected line ending value (CR, LF, CRLF, or empty string for none)

        Returns
        -------
        GcodeParser
            The GcodeParser instance created for the test, so additional assertions may be applied
            against its properties.
        """
        unit = GcodeParser()
        result = unit.parse(source, offset)
        self.assertParserProperties(
            unit=unit,
            source=source,
            offset=offset,
            length=length,
            leadingWhitespace=leadingWhitespace,
            text=text,
            trailingWhitespace=trailingWhitespace,
            lineNumber=lineNumber,
            type=type,
            code=code,
            subCode=subCode,
            parameters=parameters,
            parameterDict=parameterDict,
            rawChecksum=rawChecksum,
            checksum=checksum,
            comment=comment,
            eol=eol
        )
        self.assertEqual(result, unit, "The instance should be returned")