1 //===-- flang/unittests/RuntimeGTest/ExternalIOTest.cpp ---------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Sanity test for all external I/O modes
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "CrashHandlerFixture.h"
14 #include "gtest/gtest.h"
15 #include "flang/Runtime/io-api.h"
16 #include "flang/Runtime/main.h"
17 #include "flang/Runtime/stop.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <cstring>
20 #include <string_view>
21 
22 using namespace Fortran::runtime::io;
23 
24 struct ExternalIOTests : public CrashHandlerFixture {};
25 
26 TEST(ExternalIOTests, TestDirectUnformatted) {
27   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
28   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH')
29   Cookie io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
30   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
31   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
32   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
33 
34   std::int64_t buffer;
35   static constexpr std::size_t recl{sizeof buffer};
36   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
37   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
38 
39   int unit{-1};
40   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
41   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
42       << "EndIoStatement() for OpenNewUnit";
43 
44   // INQUIRE(IOLENGTH=) j
45   io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
46   ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
47       io, reinterpret_cast<const char *>(&buffer), 1, recl))
48       << "OutputUnformattedBlock() for InquireIoLength";
49   ASSERT_EQ(IONAME(GetIoLength)(io), recl) << "GetIoLength";
50   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
51       << "EndIoStatement() for InquireIoLength";
52 
53   static constexpr int records{10};
54   for (int j{1}; j <= records; ++j) {
55     // WRITE(UNIT=unit,REC=j) j
56     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
57     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
58 
59     buffer = j;
60     ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
61         io, reinterpret_cast<const char *>(&buffer), 1, recl))
62         << "OutputUnformattedBlock()";
63 
64     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
65         << "EndIoStatement() for OutputUnformattedBlock";
66   }
67 
68   for (int j{records}; j >= 1; --j) {
69     // READ(UNIT=unit,REC=j) n
70     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
71     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
72     ASSERT_TRUE(IONAME(InputUnformattedBlock)(
73         io, reinterpret_cast<char *>(&buffer), 1, recl))
74         << "InputUnformattedBlock()";
75 
76     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
77         << "EndIoStatement() for InputUnformattedBlock";
78 
79     ASSERT_EQ(buffer, j) << "Read back " << buffer
80                          << " from direct unformatted record " << j
81                          << ", expected " << j << '\n';
82   }
83   // CLOSE(UNIT=unit,STATUS='DELETE')
84   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
85   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
86   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
87       << "EndIoStatement() for Close";
88 }
89 
90 TEST(ExternalIOTests, TestDirectUnformattedSwapped) {
91   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
92   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH',CONVERT='NATIVE')
93   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
94   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
95   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
96   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
97   ASSERT_TRUE(IONAME(SetConvert)(io, "NATIVE", 6)) << "SetConvert(NATIVE)";
98 
99   std::int64_t buffer;
100   static constexpr std::size_t recl{sizeof buffer};
101   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
102   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
103 
104   int unit{-1};
105   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
106   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
107       << "EndIoStatement() for OpenNewUnit";
108 
109   static constexpr int records{10};
110   for (int j{1}; j <= records; ++j) {
111     // WRITE(UNIT=unit,REC=j) j
112     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
113     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
114     buffer = j;
115     ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
116         io, reinterpret_cast<const char *>(&buffer), recl, recl))
117         << "OutputUnformattedBlock()";
118     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
119         << "EndIoStatement() for OutputUnformattedBlock";
120   }
121 
122   // OPEN(UNIT=unit,STATUS='OLD',CONVERT='SWAP')
123   io = IONAME(BeginOpenUnit)(unit, __FILE__, __LINE__);
124   ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)";
125   ASSERT_TRUE(IONAME(SetConvert)(io, "SWAP", 4)) << "SetConvert(SWAP)";
126   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
127       << "EndIoStatement() for OpenUnit";
128 
129   for (int j{records}; j >= 1; --j) {
130     // READ(UNIT=unit,REC=j) n
131     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
132     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
133     ASSERT_TRUE(IONAME(InputUnformattedBlock)(
134         io, reinterpret_cast<char *>(&buffer), recl, recl))
135         << "InputUnformattedBlock()";
136     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
137         << "EndIoStatement() for InputUnformattedBlock";
138     ASSERT_EQ(buffer >> 56, j)
139         << "Read back " << (buffer >> 56) << " from direct unformatted record "
140         << j << ", expected " << j << '\n';
141   }
142 
143   // CLOSE(UNIT=unit,STATUS='DELETE')
144   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
145   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
146   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
147       << "EndIoStatement() for Close";
148 }
149 
150 TEST(ExternalIOTests, TestSequentialFixedUnformatted) {
151   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
152   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH')
153   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
154   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
155       << "SetAccess(SEQUENTIAL)";
156   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
157   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
158 
159   std::int64_t buffer;
160   static constexpr std::size_t recl{sizeof buffer};
161 
162   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
163   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
164 
165   int unit{-1};
166   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
167   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
168       << "EndIoStatement() for OpenNewUnit";
169 
170   // INQUIRE(IOLENGTH=) j, ...
171   io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
172   for (int j{1}; j <= 3; ++j) {
173     ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
174         io, reinterpret_cast<const char *>(&buffer), 1, recl))
175         << "OutputUnformattedBlock() for InquireIoLength";
176   }
177   ASSERT_EQ(IONAME(GetIoLength)(io), 3 * recl) << "GetIoLength";
178   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
179       << "EndIoStatement() for InquireIoLength";
180 
181   static const int records{10};
182   for (int j{1}; j <= records; ++j) {
183     // DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO
184     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
185     buffer = j;
186     ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
187         io, reinterpret_cast<const char *>(&buffer), recl, recl))
188         << "OutputUnformattedBlock()";
189     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
190         << "EndIoStatement() for OutputUnformattedBlock";
191   }
192 
193   // REWIND(UNIT=unit)
194   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
195   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
196       << "EndIoStatement() for Rewind";
197 
198   for (int j{1}; j <= records; ++j) {
199     // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO
200     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
201     ASSERT_TRUE(IONAME(InputUnformattedBlock)(
202         io, reinterpret_cast<char *>(&buffer), recl, recl))
203         << "InputUnformattedBlock()";
204     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
205         << "EndIoStatement() for InputUnformattedBlock";
206     ASSERT_EQ(buffer, j) << "Read back " << buffer
207                          << " from sequential fixed unformatted record " << j
208                          << ", expected " << j << '\n';
209   }
210 
211   for (int j{records}; j >= 1; --j) {
212     // BACKSPACE(UNIT=unit)
213     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
214     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
215         << "EndIoStatement() for Backspace (before read)";
216     // READ(UNIT=unit) n
217     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
218     ASSERT_TRUE(IONAME(InputUnformattedBlock)(
219         io, reinterpret_cast<char *>(&buffer), recl, recl))
220         << "InputUnformattedBlock()";
221     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
222         << "EndIoStatement() for InputUnformattedBlock";
223     ASSERT_EQ(buffer, j) << "Read back " << buffer
224                          << " from sequential fixed unformatted record " << j
225                          << " after backspacing, expected " << j << '\n';
226     // BACKSPACE(UNIT=unit)
227     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
228     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
229         << "EndIoStatement() for Backspace (after read)";
230   }
231 
232   // CLOSE(UNIT=unit,STATUS='DELETE')
233   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
234   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
235   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
236       << "EndIoStatement() for Close";
237 }
238 
239 TEST(ExternalIOTests, TestSequentialVariableUnformatted) {
240   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
241   //   FORM='UNFORMATTED',STATUS='SCRATCH')
242   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
243 
244   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
245       << "SetAccess(SEQUENTIAL)";
246   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
247   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
248   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
249 
250   int unit{-1};
251   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
252   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
253       << "EndIoStatement() for OpenNewUnit";
254 
255   static const int records{10};
256   std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)]
257   for (int j{0}; j < records; ++j) {
258     buffer[j] = j;
259   }
260 
261   for (int j{1}; j <= records; ++j) {
262     // DO J=1,RECORDS; WRITE(UNIT=unit) BUFFER(0:j); END DO
263     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
264     ASSERT_TRUE(IONAME(OutputUnformattedBlock)(io,
265         reinterpret_cast<const char *>(&buffer), j * sizeof *buffer,
266         sizeof *buffer))
267         << "OutputUnformattedBlock()";
268     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
269         << "EndIoStatement() for OutputUnformattedBlock";
270   }
271 
272   // REWIND(UNIT=unit)
273   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
274   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
275       << "EndIoStatement() for Rewind";
276   for (int j{1}; j <= records; ++j) {
277     // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO
278     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
279     ASSERT_TRUE(IONAME(InputUnformattedBlock)(io,
280         reinterpret_cast<char *>(&buffer), j * sizeof *buffer, sizeof *buffer))
281         << "InputUnformattedBlock()";
282     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
283         << "EndIoStatement() for InputUnformattedBlock";
284     for (int k{0}; k < j; ++k) {
285       ASSERT_EQ(buffer[k], k) << "Read back [" << k << "]=" << buffer[k]
286                               << " from direct unformatted record " << j
287                               << ", expected " << k << '\n';
288     }
289   }
290 
291   for (int j{records}; j >= 1; --j) {
292     // BACKSPACE(unit)
293     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
294     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
295         << "EndIoStatement() for Backspace (before read)";
296     // READ(unit=unit) n; check
297     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
298     ASSERT_TRUE(IONAME(InputUnformattedBlock)(io,
299         reinterpret_cast<char *>(&buffer), j * sizeof *buffer, sizeof *buffer))
300         << "InputUnformattedBlock()";
301     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
302         << "EndIoStatement() for InputUnformattedBlock";
303     for (int k{0}; k < j; ++k) {
304       ASSERT_EQ(buffer[k], k) << "Read back [" << k << "]=" << buffer[k]
305                               << " from sequential variable unformatted record "
306                               << j << ", expected " << k << '\n';
307     }
308     // BACKSPACE(unit)
309     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
310     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
311         << "EndIoStatement() for Backspace (after read)";
312   }
313 
314   // CLOSE(UNIT=unit,STATUS='DELETE')
315   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
316   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
317   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
318       << "EndIoStatement() for Close";
319 }
320 
321 TEST(ExternalIOTests, TestDirectFormatted) {
322   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
323   //   FORM='FORMATTED',RECL=8,STATUS='SCRATCH')
324   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
325   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
326   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
327   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
328 
329   static constexpr std::size_t recl{8};
330   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
331   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
332 
333   int unit{-1};
334   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
335   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
336       << "EndIoStatement() for OpenNewUnit";
337 
338   static constexpr int records{10};
339   static const char fmt[]{"(I4)"};
340   for (int j{1}; j <= records; ++j) {
341     // WRITE(UNIT=unit,FMT=fmt,REC=j) j
342     io = IONAME(BeginExternalFormattedOutput)(
343         fmt, sizeof fmt - 1, unit, __FILE__, __LINE__);
344     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
345     ASSERT_TRUE(IONAME(OutputInteger64)(io, j)) << "OutputInteger64()";
346     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
347         << "EndIoStatement() for OutputInteger64";
348   }
349 
350   for (int j{records}; j >= 1; --j) {
351     // READ(UNIT=unit,FMT=fmt,REC=j) n
352     io = IONAME(BeginExternalFormattedInput)(
353         fmt, sizeof fmt - 1, unit, __FILE__, __LINE__);
354     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
355     std::int64_t buffer;
356     ASSERT_TRUE(IONAME(InputInteger)(io, buffer)) << "InputInteger()";
357     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
358         << "EndIoStatement() for InputInteger";
359 
360     ASSERT_EQ(buffer, j) << "Read back " << buffer
361                          << " from direct formatted record " << j
362                          << ", expected " << j << '\n';
363   }
364 
365   // CLOSE(UNIT=unit,STATUS='DELETE')
366   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
367   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
368   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
369       << "EndIoStatement() for Close";
370 }
371 
372 TEST(ExternalIOTests, TestSequentialVariableFormatted) {
373   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
374   //   FORM='FORMATTED',STATUS='SCRATCH')
375   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
376   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
377       << "SetAccess(SEQUENTIAL)";
378   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
379   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
380   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
381 
382   int unit{-1};
383   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
384   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
385       << "EndIoStatement() for OpenNewUnit";
386 
387   static const int records{10};
388   std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)]
389   for (int j{0}; j < records; ++j) {
390     buffer[j] = j;
391   }
392 
393   char fmt[32];
394   for (int j{1}; j <= records; ++j) {
395     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
396     // DO J=1,RECORDS; WRITE(UNIT=unit,FMT=fmt) BUFFER(0:j); END DO
397     io = IONAME(BeginExternalFormattedOutput)(
398         fmt, std::strlen(fmt), unit, __FILE__, __LINE__);
399     for (int k{0}; k < j; ++k) {
400       ASSERT_TRUE(IONAME(OutputInteger64)(io, buffer[k]))
401           << "OutputInteger64()";
402     }
403     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
404         << "EndIoStatement() for OutputInteger64";
405   }
406 
407   // REWIND(UNIT=unit)
408   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
409   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
410       << "EndIoStatement() for Rewind";
411 
412   for (int j{1}; j <= records; ++j) {
413     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
414     // DO J=1,RECORDS; READ(UNIT=unit,FMT=fmt) n; check n; END DO
415     io = IONAME(BeginExternalFormattedInput)(
416         fmt, std::strlen(fmt), unit, __FILE__, __LINE__);
417 
418     std::int64_t check[records];
419     for (int k{0}; k < j; ++k) {
420       ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()";
421     }
422     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
423         << "EndIoStatement() for InputInteger";
424 
425     for (int k{0}; k < j; ++k) {
426       ASSERT_EQ(buffer[k], check[k])
427           << "Read back [" << k << "]=" << check[k]
428           << " from sequential variable formatted record " << j << ", expected "
429           << buffer[k] << '\n';
430     }
431   }
432 
433   for (int j{records}; j >= 1; --j) {
434     // BACKSPACE(unit)
435     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
436     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
437         << "EndIoStatement() for Backspace (before read)";
438 
439     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
440     // READ(UNIT=unit,FMT=fmt,SIZE=chars) n; check
441     io = IONAME(BeginExternalFormattedInput)(
442         fmt, std::strlen(fmt), unit, __FILE__, __LINE__);
443 
444     std::int64_t check[records];
445     for (int k{0}; k < j; ++k) {
446       ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()";
447     }
448 
449     std::size_t chars{IONAME(GetSize)(io)};
450     ASSERT_EQ(chars, j * 4u)
451         << "GetSize()=" << chars << ", expected " << (j * 4u) << '\n';
452     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
453         << "EndIoStatement() for InputInteger";
454     for (int k{0}; k < j; ++k) {
455       ASSERT_EQ(buffer[k], check[k])
456           << "Read back [" << k << "]=" << buffer[k]
457           << " from sequential variable formatted record " << j << ", expected "
458           << buffer[k] << '\n';
459     }
460 
461     // BACKSPACE(unit)
462     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
463     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
464         << "EndIoStatement() for Backspace (after read)";
465   }
466 
467   // CLOSE(UNIT=unit,STATUS='DELETE')
468   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
469   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
470   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
471       << "EndIoStatement() for Close";
472 }
473 
474 TEST(ExternalIOTests, TestNonAvancingInput) {
475   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
476   //   FORM='FORMATTED',STATUS='SCRATCH')
477   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
478   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
479       << "SetAccess(SEQUENTIAL)";
480   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
481   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
482   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
483 
484   int unit{-1};
485   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
486   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
487       << "EndIoStatement() for OpenNewUnit";
488 
489   // Write the file to be used for the input test.
490   static constexpr std::string_view records[] = {
491       "ABCDEFGH", "IJKLMNOP", "QRSTUVWX"};
492   static constexpr std::string_view fmt{"(A)"};
493   for (const auto &record : records) {
494     // WRITE(UNIT=unit,FMT=fmt) record
495     io = IONAME(BeginExternalFormattedOutput)(
496         fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
497     ASSERT_TRUE(IONAME(OutputAscii)(io, record.data(), record.length()))
498         << "OutputAscii()";
499     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
500         << "EndIoStatement() for OutputAscii";
501   }
502 
503   // REWIND(UNIT=unit)
504   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
505   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
506       << "EndIoStatement() for Rewind";
507 
508   struct TestItems {
509     std::string item;
510     int expectedIoStat;
511     std::string expectedItemValue;
512   };
513   // Actual non advancing input IO test
514   TestItems inputItems[]{
515       {std::string(4, '+'), IostatOk, "ABCD"},
516       {std::string(4, '+'), IostatOk, "EFGH"},
517       {std::string(4, '+'), IostatEor, "    "},
518       {std::string(2, '+'), IostatOk, "IJ"},
519       {std::string(8, '+'), IostatEor, "KLMNOP  "},
520       {std::string(10, '+'), IostatEor, "QRSTUVWX  "},
521   };
522 
523   int j{0};
524   for (auto &inputItem : inputItems) {
525     // READ(UNIT=unit, FMT=fmt, ADVANCE='NO', IOSTAT=iostat) inputItem
526     io = IONAME(BeginExternalFormattedInput)(
527         fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
528     IONAME(EnableHandlers)(io, true, false, false, false, false);
529     ASSERT_TRUE(IONAME(SetAdvance)(io, "NO", 2)) << "SetAdvance(NO)" << j;
530     ASSERT_TRUE(
531         IONAME(InputAscii)(io, inputItem.item.data(), inputItem.item.length()))
532         << "InputAscii() " << j;
533     ASSERT_EQ(IONAME(EndIoStatement)(io), inputItem.expectedIoStat)
534         << "EndIoStatement() for Read " << j;
535     ASSERT_EQ(inputItem.item, inputItem.expectedItemValue)
536         << "Input-item value after non advancing read " << j;
537     j++;
538   }
539 }
540