diff --git a/lib/gis/getl.c b/lib/gis/getl.c index 7daae816a70..34df9b04ad0 100644 --- a/lib/gis/getl.c +++ b/lib/gis/getl.c @@ -19,8 +19,7 @@ * \brief Gets a line of text from a file * * This routine runs fgets() to fetch a line of text from a file - * (advancing file pointer) and removes trailing newline. fgets() does - * not recognize '\\r' as an EOL and will read past * it. + * (advancing file pointer) and removes trailing newline. * * \param buf string buffer to receive read data * \param n maximum number of bytes to read @@ -28,23 +27,18 @@ * * \return 1 on success * \return 0 EOF + * + * \see G_getl2() */ int G_getl(char *buf, int n, FILE *fd) { - if (!fgets(buf, n, fd)) - return 0; - - for (; *buf && *buf != '\n'; buf++) - ; - *buf = 0; - - return 1; + return G_getl2(buf, n, fd); } /*! * \brief Gets a line of text from a file of any pedigree * - * This routine is like G_getl() but is more portable. It supports + * This routine supports * text files created on various platforms (UNIX, MacOS9, DOS), * i.e. \\n (\\012), \\r (\\015), and * \\r\\n (\\015\\012) style newlines. diff --git a/lib/gis/testsuite/test_gis_lib_getl.py b/lib/gis/testsuite/test_gis_lib_getl.py new file mode 100644 index 00000000000..98092955ab2 --- /dev/null +++ b/lib/gis/testsuite/test_gis_lib_getl.py @@ -0,0 +1,81 @@ +"""Test of gis library line reading functions + +@author Vaclav Petras +""" + +import ctypes +import pathlib +import platform +import unittest + +import grass.lib.gis as libgis +from grass.gunittest.case import TestCase +from grass.gunittest.main import test + + +class TestNewlinesWithGetlFunctions(TestCase): + """Test C functions G_getl() and G_getl2() from gis library""" + + @classmethod + def setUpClass(cls): + cls.libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c")) + cls.libc.fopen.restype = ctypes.POINTER(libgis.FILE) + cls.libc.fopen.argtypes = [ctypes.c_char_p, ctypes.c_char_p] + cls.file_path = pathlib.Path("test.txt") + + def tearDown(self): + self.file_path.unlink() + + def read_lines_and_assert(self, get_line_function, newline): + """Write and read lines and then assert they are as expected""" + lines = ["Line 1", "Line 2", "Line 3"] + with open(self.file_path, mode="w", newline=newline) as stream: + for line in lines: + # Python text newline here. + # The specific newline is added by the stream. + stream.write(f"{line}\n") + + file_ptr = self.libc.fopen(str(self.file_path).encode("utf-8"), b"r") + if not file_ptr: + raise FileNotFoundError(f"Could not open file: {self.file_path}") + + try: + buffer_size = 50 + buffer = ctypes.create_string_buffer(buffer_size) + + for line in lines: + get_line_function(buffer, ctypes.sizeof(buffer), file_ptr) + result = buffer.value.decode("utf-8") if buffer else None + self.assertEqual(line, result) + finally: + self.libc.fclose(file_ptr) + + def test_getl_lf(self): + r"""Check G_getl() with LF (\n)""" + self.read_lines_and_assert(libgis.G_getl, "\n") + + @unittest.expectedFailure + def test_getl_cr(self): + r"""Check G_getl() with CR (\r)""" + self.read_lines_and_assert(libgis.G_getl, "\r") + + def test_getl_crlf(self): + r"""Check G_getl() with CRLF (\r\n)""" + self.read_lines_and_assert(libgis.G_getl, "\r\n") + + def test_getl2_lf(self): + r"""Check G_getl2() with LF (\n)""" + self.read_lines_and_assert(libgis.G_getl2, "\n") + + @unittest.expectedFailure + def test_getl2_cr(self): + r"""Check G_getl2() with CR (\r)""" + self.read_lines_and_assert(libgis.G_getl2, "\r") + + def test_getl2_crlf(self): + r"""Check G_getl2() with CRLF (\r\n)""" + self.read_lines_and_assert(libgis.G_getl2, "\r\n") + + +if __name__ == "__main__": + test()