1unit tcbufferedfilestream;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8  Classes, SysUtils, fpcunit, testregistry, bufstream;
9
10type
11
12  { TTestBufferedFileStream }
13
14  TTestBufferedFileStream= class(TTestCase)
15  private
16  const
17    TEST_RANDOM_READS=10000;
18    TEST_SEQUENTIAL_READS=1000000;
19    TEST_FILENAME='testfile.bin';
20    TEST_WRITEC_FILE='testwritecache.bin';
21    TEST_WRITEF_FILE='testwritedirec.bin';
22  private
23    function CompareStreams(const aStream1: TStream; const aStream2: TStream): Boolean;
24  protected
25    procedure SetUp; override;
26    procedure TearDown; override;
27  published
28    procedure TestCacheRead;
29    procedure TestCacheWrite;
30    procedure TestCacheSeek;
31  end;
32
33implementation
34
35procedure TTestBufferedFileStream.TestCacheRead;
36var
37  lBufferedStream: TBufferedFileStream;
38  lStream: TFileStream;
39  b: array [0..10000-1] of char;
40  j,k: integer;
41  lBytesToRead: integer;
42  lEffectiveRead: integer;
43  {$IFDEF CHECK_AGAINST_FILE}
44  lEffectiveRead2: integer;
45  {$ENDIF}
46  lReadPosition: int64;
47  lCheckInitV: integer;
48  lTick: QWord;
49begin
50  b[0]:=#0; // Avoid initalization hint
51  lBufferedStream:=TBufferedFileStream.Create(TEST_FILENAME,fmOpenRead or fmShareDenyWrite);
52  lStream:=TFileStream.Create(TEST_FILENAME,fmOpenRead or fmShareDenyWrite);
53  try
54    RandSeed:=1;
55    Randomize;
56    lTick:=GetTickCount64;
57    for j := 0 to Pred(TEST_RANDOM_READS) do begin
58      lBytesToRead:=Random(10000);
59      lReadPosition:=Random(lBufferedStream.Size);
60      lBufferedStream.Position:=lReadPosition;
61
62      lEffectiveRead:=lBufferedStream.Read(b,lBytesToRead);
63
64      {$IFDEF CHECK_AGAINST_FILE}
65      // Now read without cache
66      lStream.Position:=lReadPosition;
67      lEffectiveRead2:=lStream.Read(b2,lBytesToRead);
68      if lEffectiveRead<>lEffectiveRead2 then begin
69        FAIL('Read length mismatch');
70      end;
71      if not CompareMem(@b[0],@b2[0],lEffectiveRead) then begin
72        FAIL('Compare buffer data error');
73      end;
74      F.Position:=0;
75      {$ELSE}
76      lCheckInitV:=lReadPosition mod 10;
77      for k := 0 to Pred(lEffectiveRead) do begin
78        if b[k]<>char(ord('0')+lCheckInitV mod 10) then begin
79          FAIL('Expected data error');
80        end;
81        inc(lCheckInitV);
82      end;
83      {$ENDIF}
84    end;
85    // Writeln('CACHE ',TEST_RANDOM_READS,' random reads in ',GetTickCount64-lTick,' ms.');
86
87    RandSeed:=1;
88    Randomize;
89
90    // Writeln('Same operation without cache');
91    lTick:=GetTickCount64;
92    for j := 0 to Pred(TEST_RANDOM_READS) do begin
93      lBytesToRead:=Random(10000);
94      lReadPosition:=Random(lBufferedStream.Size);
95
96      lStream.Position:=lReadPosition;
97      lEffectiveRead:=lStream.Read(b,lBytesToRead);
98
99      lCheckInitV:=lReadPosition mod 10;
100      for k := 0 to Pred(lEffectiveRead) do begin
101        if b[k]<>char(ord('0')+lCheckInitV mod 10) then begin
102          FAIL('Expected data error');
103        end;
104        inc(lCheckInitV);
105      end;
106    end;
107    // Writeln('FILE ',TEST_RANDOM_READS,' random reads in ',GetTickCount64-lTick,' ms.');
108
109    // Writeln('Check sequential read');
110
111    RandSeed:=1;
112    Randomize;
113    lTick:=GetTickCount64;
114    lBytesToRead:=1;
115    lReadPosition:=0;
116    lBufferedStream.Position:=lReadPosition;
117    lStream.Position:=lReadPosition;
118    for j := 0 to Pred(TEST_SEQUENTIAL_READS) do begin
119
120      lEffectiveRead:=lBufferedStream.Read(b,lBytesToRead);
121
122      {$IFDEF CHECK_AGAINST_FILE}
123      // Now read without cache
124      lEffectiveRead2:=lStream.Read(b2,lBytesToRead);
125      if lEffectiveRead<>lEffectiveRead2 then begin
126        FAIL('Read length mismatch');
127      end;
128      if not CompareMem(@b[0],@b2[0],lEffectiveRead) then begin
129        FAIL('Compare buffer data error');
130      end;
131      F.Position:=0;
132      {$ELSE}
133      lCheckInitV:=lReadPosition mod 10;
134      for k := 0 to Pred(lEffectiveRead) do begin
135        if b[k]<>char(ord('0')+lCheckInitV mod 10) then begin
136          FAIL('Expected data error');
137        end;
138        inc(lCheckInitV);
139      end;
140      {$ENDIF}
141      inc(lReadPosition,lBytesToRead);
142    end;
143    // Writeln('CACHE ',TEST_SEQUENTIAL_READS,' byte sequential reads in ',GetTickCount64-lTick,' ms.');
144
145    RandSeed:=1;
146    Randomize;
147    lTick:=GetTickCount64;
148    lBytesToRead:=1;
149    lReadPosition:=0;
150    lStream.Position:=lReadPosition;
151    for j := 0 to Pred(TEST_SEQUENTIAL_READS) do begin
152
153      lEffectiveRead:=lStream.Read(b,lBytesToRead);
154
155      lCheckInitV:=lReadPosition mod 10;
156      for k := 0 to Pred(lEffectiveRead) do begin
157        if b[k]<>char(ord('0')+lCheckInitV mod 10) then begin
158          FAIL('Expected data error');
159        end;
160        inc(lCheckInitV);
161      end;
162      inc(lReadPosition,lBytesToRead);
163    end;
164    // Writeln('FILE ',TEST_SEQUENTIAL_READS,' byte sequential reads in ',GetTickCount64-lTick,' ms.');
165
166    // Writeln('CACHE Trying read beyond limits');
167    lBufferedStream.Position:=lBufferedStream.Size-1;
168    lEffectiveRead:=lBufferedStream.Read(b,2);
169    if lEffectiveRead<>1 then begin
170      FAIL('Read beyond limits, returned bytes: '+inttostr(lEffectiveRead));
171    end else begin
172      // Writeln('CACHE OK, read beyond limits returns 0 bytes.');
173    end;
174  finally
175    lBufferedStream.Free;
176    lStream.Free;
177  end;
178end;
179
180procedure TTestBufferedFileStream.TestCacheWrite;
181const
182  EXPECTED_SIZE=10000000;
183  TEST_ROUNDS=100000;
184var
185  lBufferedStream: TBufferedFileStream;
186  lStream: TFileStream;
187  lVerifyStream1,lVerifyStream2: TFileStream;
188  b: array [0..10000-1] of char;
189  j: integer;
190  lBytesToWrite: integer;
191  lWritePosition: int64;
192begin
193  // Writeln('Testing write cache');
194  // All test should return the same random sequence
195  RandSeed:=1;
196  Randomize;
197  for j := 0 to Pred(10000) do begin
198    b[j]:='0';
199  end;
200  lBufferedStream:=TBufferedFileStream.Create(TEST_WRITEC_FILE,fmCreate);
201  lStream:=TFileStream.Create(TEST_WRITEF_FILE,fmCreate);
202  try
203    for j := 0 to Pred(EXPECTED_SIZE div Sizeof(b)) do begin
204      lBufferedStream.Write(b,sizeof(b));
205      lStream.Write(b,sizeof(b));
206    end;
207    for j := 0 to Pred(Sizeof(b)) do begin
208      b[j]:=char(ord('0')+j mod 10);
209    end;
210  finally
211    lBufferedStream.Free;
212    lStream.Free;
213  end;
214  lBufferedStream:=TBufferedFileStream.Create(TEST_WRITEC_FILE,fmOpenReadWrite);
215  lStream:=TFileStream.Create(TEST_WRITEF_FILE,fmOpenWrite);
216  try
217    for j := 0 to Pred(TEST_ROUNDS) do begin
218      if lStream.Size<>lBufferedStream.Size then begin
219        FAIL('Mismatched lengths');
220      end;
221      lWritePosition:=Random(EXPECTED_SIZE);
222      lBytesToWrite:=Random(sizeof(b));
223      lBufferedStream.Position:=lWritePosition;
224      lStream.Position:=lWritePosition;
225      lBufferedStream.Write(b,lBytesToWrite);
226      lStream.Write(b,lBytesToWrite);
227      // if j mod 1273 = 0 then write(j,' / ',TEST_ROUNDS,#13);
228    end;
229    // Writeln(TEST_ROUNDS,' / ',TEST_ROUNDS);
230    if lStream.Size<>lBufferedStream.Size then begin
231      FAIL('Mismatched lengths');
232    end;
233  finally
234    lBufferedStream.Free;
235    lStream.Free;
236  end;
237
238  // Verify both generated files are identical.
239  lVerifyStream1:=TFileStream.Create(TEST_WRITEC_FILE,fmOpenRead or fmShareDenyWrite);
240  lVerifyStream2:=TFileStream.Create(TEST_WRITEF_FILE,fmOpenRead or fmShareDenyWrite);
241  try
242    if not CompareStreams(lVerifyStream1,lVerifyStream2) then begin
243      FAIL('Streams are different!!');
244    end else begin
245      // Writeln('Streams are identical. OK.');
246    end;
247  finally
248    lVerifyStream1.Free;
249    lVerifyStream2.Free;
250  end;
251end;
252
253procedure TTestBufferedFileStream.TestCacheSeek;
254var
255  lBufferedStream: TBufferedFileStream;
256  lStream: TFileStream;
257  bBuffered: array [0..10000] of BYTE;
258  bStream: array [0..10000] of BYTE;
259  bread : Integer;
260
261begin
262  bBuffered[0]:=0; // Avoid initalization hint
263  bStream[0]:=0; // Avoid initalization hint
264  lBufferedStream:=TBufferedFileStream.Create(TEST_FILENAME,fmOpenRead or fmShareDenyWrite);
265  lStream:=TFileStream.Create(TEST_FILENAME,fmOpenRead or fmShareDenyWrite);
266  try
267    // Writeln('Set position=-1');
268    lStream.Position:=-1;
269    // Writeln('TFileStream position=',lStream.Position);
270    lBufferedStream.Position:=-1;
271    // Writeln('Buffered    position=',lBufferedStream.Position);
272    if lStream.Position<>lBufferedStream.Position then begin
273      FAIL('Positions are not the same.');
274    end else begin
275      // Writeln('Positions are the same.');
276    end;
277
278    // Writeln('Read data when position=-1');
279    bread:=lStream.Read(bBuffered[0],10);
280     // Writeln('TFileStream read bytes  : ',bread);
281     // Writeln('TFileStream end position: ',lStream.Position);
282    bread:=lBufferedStream.Read(bStream[0],10);
283     // Writeln('Buffered      read bytes: ',bread);
284     // Writeln('Buffered    end position: ',lBufferedStream.Position);
285    if (not CompareMem(@bBuffered[0],@bStream[0],10)) or (lStream.Position<>lBufferedStream.Position) then begin
286      FAIL('Read data or positions are not the same.');
287    end else begin
288      // Writeln('Read data at -1 is the same.');
289    end;
290
291    // Writeln('Testing Seek operations');
292    // Writeln('Seek -1 from beginning');
293    bread:=lStream.Seek(-1,soBeginning);
294    // Writeln('Stream seek result  : ',bread);
295    bread:=lBufferedStream.Seek(-1,soBeginning);
296    // Writeln('Buffered seek result: ',);
297
298    // Writeln('Read data when Seek -1');
299    bread:=lStream.Read(bBuffered[0],10);
300    // Writeln('TFileStream read bytes  : ',bread);
301    // Writeln('TFileStream end position: ',lStream.Position);
302    bread:=lBufferedStream.Read(bStream[0],10);
303    // Writeln('Buffered      read bytes: ',bread);
304    // Writeln('Buffered    end position: ',lBufferedStream.Position);
305    if (not CompareMem(@bBuffered[0],@bStream[0],10)) or (lStream.Position<>lBufferedStream.Position) then begin
306      FAIL('Read data or positions are not the same.');
307    end else begin
308      // Writeln('Read data at -1 is the same.');
309    end;
310
311    // Writeln('Seek -current*2 from current');
312    bread:=lStream.Seek(lStream.Position*-2,soCurrent);
313    // Writeln('Stream seek result  : ',bread);
314    bread:=lBufferedStream.Seek(lBufferedStream.Position*-2,soCurrent);
315    // Writeln('Buffered seek result: ',bread);
316    // Writeln('Read data when Seek from current -current*2');
317    bread:=lStream.Read(bBuffered[0],10);
318    // Writeln('TFileStream read bytes  : ',bread);
319    // Writeln('TFileStream end position: ',lStream.Position);
320    bread:=lBufferedStream.Read(bStream[0],10);
321    // Writeln('Buffered      read bytes: ',);
322    // Writeln('Buffered    end position: ',lBufferedStream.Position);
323    if (not CompareMem(@bBuffered[0],@bStream[0],10)) or (lStream.Position<>lBufferedStream.Position) then begin
324      FAIL('Read data or positions are not the same.');
325    end else begin
326      // Writeln('Read data at -current*2 is the same.');
327    end;
328  finally
329    lBufferedStream.Free;
330    lStream.Free;
331  end;
332end;
333
334procedure TTestBufferedFileStream.SetUp;
335var
336  F: TFileStream;
337  b: array [0..10000-1] of char;
338  j: integer;
339begin
340  for j := 0 to Pred(10000) do begin
341    b[j]:=char(ord('0')+j mod 10);
342  end;
343  F:=TFileStream.Create(TEST_FILENAME,fmCreate);
344  for j := 0 to Pred(1000) do begin
345    F.Write(b,sizeof(b));
346  end;
347  F.Free;
348end;
349
350procedure TTestBufferedFileStream.TearDown;
351begin
352  DeleteFile(TEST_FILENAME);
353  DeleteFile(TEST_WRITEC_FILE);
354  DeleteFile(TEST_WRITEF_FILE);
355end;
356
357function TTestBufferedFileStream.CompareStreams(const aStream1: TStream;
358  const aStream2: TStream): Boolean;
359const
360  BUFFER_SIZE=5213; // Odd number
361var
362  b1: array [0..BUFFER_SIZE-1] of BYTE;
363  b2: array [0..BUFFER_SIZE-1] of BYTE;
364  lReadBytes: integer;
365  lAvailable: integer;
366  lEffectiveRead1: integer;
367  lEffectiveRead2: integer;
368begin
369  b1[0]:=0; // Avoid initalization hint
370  b2[0]:=0; // Avoid initalization hint
371  Result:=false;
372  if aStream1.Size<>aStream2.Size then exit;
373  aStream1.Position:=0;
374  aStream2.Position:=0;
375  while aStream1.Position<aStream1.Size do begin
376    lAvailable:=aStream1.Size-aStream1.Position;
377    if lAvailable>=BUFFER_SIZE then begin
378      lReadBytes:=BUFFER_SIZE;
379    end else begin
380      lReadBytes:=aStream1.Size-aStream1.Position;
381    end;
382    lEffectiveRead1:=aStream1.Read(b1[0],lReadBytes);
383    lEffectiveRead2:=aStream2.Read(b2[0],lReadBytes);
384    if lEffectiveRead1<>lEffectiveRead2 then exit;
385    if not CompareMem(@b1[0],@b2[0],lEffectiveRead1) then exit;
386  end;
387  Result:=true;
388end;
389
390initialization
391  RegisterTest(TTestBufferedFileStream);
392end.
393
394