thecodingidiot.com

The ReaderEdge Cases

Edge Cases

The tester exercises several inputs that expose weaknesses in a naive implementation. This page works through each one.

BUFFER_SIZE = 1

The tester recompiles with -D BUFFER_SIZE=1 and runs the same suite. Every read() call returns exactly one byte. The loop runs once per character.

The algorithm does not change — strjoin accumulates one-character chunks until a '\n' is found. The only question is whether the implementation actually compiles and works correctly when the buffer holds a single byte.

Check tci_getline.c for any assumption that BUFFER_SIZE > 1. The array declaration char buf[BUFFER_SIZE + 1] is correct — at BUFFER_SIZE=1 it allocates buf[2], which holds one byte plus the null terminator added on line buf[bytes] = '\0'.

No trailing newline at EOF

A file that ends without '\n':

first line
second line
third line without newline

After reading "third line without newline" into leftover, read() returns 0. flush_leftover checks whether leftover is non-empty — it is — and returns the string without a '\n' at the end. The caller receives the last line without modification.

This is correct behaviour: the line exists, it just lacks a terminating newline. The caller cannot distinguish "line with newline stripped" from "line that never had one" — nor should it need to.

Empty file

open succeeds. The first read() returns 0 immediately. leftover is the empty string "" (set by tci_strdup("") at the start of the loop). flush_leftover tests !**leftover — true for an empty string — frees it, sets leftover = NULL, and returns NULL.

Return value: NULL. Correct — no lines in an empty file.

Invalid file descriptor

read(-1, buf, BUFFER_SIZE) returns −1. flush_leftover is called with leftover holding the empty string. The same path as an empty file: return NULL.

Memory leaks

Run valgrind after reading a file to completion:

gcc -Wall -Wextra -g -std=c99 -D BUFFER_SIZE=32 -o gltest gl_test.c -L. -ltci -I. 
valgrind --leak-check=full ./gltest test.txt

Where gl_test.c reads every line until NULL:

#include "libtci.h"
#include <fcntl.h>   /* open, O_RDONLY */
#include <stdio.h>   /* printf */
#include <stdlib.h>  /* free */
 
int     main(void)
{
    int     fd;
    char    *line;
 
    fd = open("test.txt", O_RDONLY);
    while ((line = tci_getline(fd)) != NULL) {
        printf("%s", line);
        free(line);              /* caller owns every string tci_getline returns */
    }
    close(fd);
    return (0);
}

valgrind must report zero bytes lost. The two memory paths to verify:

  1. After strjoin, the old leftover is freed via tmp. No leak from accumulation.
  2. In flush_leftover, when leftover is empty, it is freed before returning NULL. When non-empty, it is returned — the caller's free(line) handles it.

Run the full suite

make re
bash test.sh

All tci_getline tests — including BUFFER_SIZE=1, no trailing newline, and empty file — should now pass. The next page adds support for reading multiple file descriptors simultaneously.