Source file src/internal/runtime/cgroup/line_reader_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cgroup_test
     6  
     7  import (
     8  	"internal/runtime/cgroup"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  type nextLine struct {
    14  	line       string
    15  	incomplete bool // next call before this line should return incomplete
    16  }
    17  
    18  func complete(s string) nextLine {
    19  	return nextLine{line: s}
    20  }
    21  func incomplete(s string) nextLine {
    22  	return nextLine{line: s, incomplete: true}
    23  }
    24  
    25  const scratchSize = 8
    26  
    27  var readerTests = []struct {
    28  	name     string
    29  	contents string
    30  	want     []nextLine
    31  }{
    32  	{
    33  		name:     "empty",
    34  		contents: "",
    35  	},
    36  	{
    37  		name:     "single",
    38  		contents: "1234\n",
    39  		want: []nextLine{
    40  			complete("1234"),
    41  		},
    42  	},
    43  	{
    44  		name:     "single-incomplete",
    45  		contents: "1234",
    46  		want: []nextLine{
    47  			incomplete("1234"),
    48  		},
    49  	},
    50  	{
    51  		name:     "single-exact",
    52  		contents: "1234567\n",
    53  		want: []nextLine{
    54  			complete("1234567"),
    55  		},
    56  	},
    57  	{
    58  		name:     "single-exact-incomplete",
    59  		contents: "12345678",
    60  		want: []nextLine{
    61  			incomplete("12345678"),
    62  		},
    63  	},
    64  	{
    65  		name: "multi",
    66  		contents: `1234
    67  5678
    68  `,
    69  		want: []nextLine{
    70  			complete("1234"),
    71  			complete("5678"),
    72  		},
    73  	},
    74  	{
    75  		name: "multi-short",
    76  		contents: `12
    77  34
    78  56
    79  78
    80  `,
    81  		want: []nextLine{
    82  			complete("12"),
    83  			complete("34"),
    84  			complete("56"),
    85  			complete("78"),
    86  		},
    87  	},
    88  	{
    89  		name: "multi-notrailingnewline",
    90  		contents: `1234
    91  5678`,
    92  		want: []nextLine{
    93  			complete("1234"),
    94  			incomplete("5678"),
    95  		},
    96  	},
    97  	{
    98  		name: "middle-too-long",
    99  		contents: `1234
   100  1234567890
   101  5678
   102  `,
   103  		want: []nextLine{
   104  			complete("1234"),
   105  			incomplete("12345678"),
   106  			complete("5678"),
   107  		},
   108  	},
   109  	{
   110  		// Multiple reads required to find newline.
   111  		name: "middle-way-too-long",
   112  		contents: `1234
   113  12345678900000000000000000000000000000000000000000000000000
   114  5678
   115  `,
   116  		want: []nextLine{
   117  			complete("1234"),
   118  			incomplete("12345678"),
   119  			complete("5678"),
   120  		},
   121  	},
   122  }
   123  
   124  func TestLineReader(t *testing.T) {
   125  	for _, tc := range readerTests {
   126  		t.Run(tc.name, func(t *testing.T) {
   127  			var scratch [scratchSize]byte
   128  			l := cgroup.NewLineReader(0, scratch[:], readString(tc.contents))
   129  
   130  			var got []nextLine
   131  			for {
   132  				err := l.Next()
   133  				if err == cgroup.ErrEOF {
   134  					break
   135  				} else if err == cgroup.ErrIncompleteLine {
   136  					got = append(got, incomplete(string(l.Line())))
   137  				} else if err != nil {
   138  					t.Fatalf("next got err %v", err)
   139  				} else {
   140  					got = append(got, complete(string(l.Line())))
   141  				}
   142  			}
   143  
   144  			if len(got) != len(tc.want) {
   145  				t.Logf("got lines %+v", got)
   146  				t.Logf("want lines %+v", tc.want)
   147  				t.Fatalf("lineReader got %d lines, want %d", len(got), len(tc.want))
   148  			}
   149  
   150  			for i := range got {
   151  				if got[i].line != tc.want[i].line {
   152  					t.Errorf("line %d got %q want %q", i, got[i].line, tc.want[i].line)
   153  				}
   154  				if got[i].incomplete != tc.want[i].incomplete {
   155  					t.Errorf("line %d got incomplete %v want %v", i, got[i].incomplete, tc.want[i].incomplete)
   156  				}
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  func FuzzLineReader(f *testing.F) {
   163  	for _, tc := range readerTests {
   164  		f.Add(tc.contents)
   165  	}
   166  	f.Fuzz(func(t *testing.T, input string) {
   167  		scratch := make([]byte, scratchSize)
   168  		reader := cgroup.NewLineReader(0, scratch, readString(input))
   169  		for expected := range strings.Lines(input) {
   170  			err := reader.Next()
   171  			line := reader.Line()
   172  
   173  			var expectedErr error
   174  			if len(expected) > scratchSize {
   175  				expected = expected[:scratchSize]
   176  				expectedErr = cgroup.ErrIncompleteLine
   177  			} else if expected[len(expected)-1] == '\n' {
   178  				expected = expected[:len(expected)-1]
   179  			} else {
   180  				expectedErr = cgroup.ErrIncompleteLine
   181  			}
   182  
   183  			if err != expectedErr {
   184  				t.Fatalf("got err %v, want %v", err, expectedErr)
   185  			}
   186  
   187  			if string(line) != expected {
   188  				t.Fatalf("got %q, want %q", string(line), expected)
   189  			}
   190  		}
   191  		err := reader.Next()
   192  		if err != cgroup.ErrEOF {
   193  			t.Fatalf("got %v, want EOF", err)
   194  		}
   195  	})
   196  }
   197  

View as plain text