xref: /llvm-project/flang/unittests/Runtime/ExternalIOTest.cpp (revision c91ba04328e1ded6f284469a7828d181324d4e30)
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/descriptor.h"
16 #include "flang/Runtime/io-api-consts.h"
17 #include "flang/Runtime/main.h"
18 #include "flang/Runtime/stop.h"
19 #include "llvm/Support/raw_ostream.h"
20 #include <cstring>
21 #include <string_view>
22 
23 using namespace Fortran::runtime;
24 using namespace Fortran::runtime::io;
25 
26 struct ExternalIOTests : public CrashHandlerFixture {};
27 
28 TEST(ExternalIOTests, TestDirectUnformatted) {
29   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
30   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH')
31   Cookie io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
32   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
33   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
34   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
35 
36   std::int64_t buffer;
37   static constexpr std::size_t recl{sizeof buffer};
38   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
39   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
40 
41   int unit{-1};
42   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
43   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
44       << "EndIoStatement() for OpenNewUnit";
45 
46   StaticDescriptor<0> staticDescriptor;
47   Descriptor &desc{staticDescriptor.descriptor()};
48   desc.Establish(TypeCode{CFI_type_int8_t}, recl, &buffer, 0);
49   desc.Check();
50 
51   // INQUIRE(IOLENGTH=) j
52   io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
53   ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
54       << "OutputDescriptor() for InquireIoLength";
55   ASSERT_EQ(IONAME(GetIoLength)(io), recl) << "GetIoLength";
56   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
57       << "EndIoStatement() for InquireIoLength";
58 
59   static constexpr int records{10};
60   for (int j{1}; j <= records; ++j) {
61     // WRITE(UNIT=unit,REC=j) j
62     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
63     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
64 
65     buffer = j;
66     ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
67         << "OutputDescriptor() for Write";
68 
69     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
70         << "EndIoStatement() for Write";
71   }
72 
73   for (int j{records}; j >= 1; --j) {
74     buffer = -1;
75     // READ(UNIT=unit,REC=j) n
76     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
77     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
78     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc))
79         << "InputDescriptor() for Read";
80 
81     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
82         << "EndIoStatement() for Read";
83 
84     ASSERT_EQ(buffer, j) << "Read back " << buffer
85                          << " from direct unformatted record " << j
86                          << ", expected " << j << '\n';
87   }
88   // CLOSE(UNIT=unit,STATUS='DELETE')
89   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
90   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
91   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
92       << "EndIoStatement() for Close";
93 }
94 
95 TEST(ExternalIOTests, TestDirectUnformattedSwapped) {
96   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
97   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH',CONVERT='NATIVE')
98   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
99   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
100   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
101   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
102   ASSERT_TRUE(IONAME(SetConvert)(io, "NATIVE", 6)) << "SetConvert(NATIVE)";
103 
104   std::int64_t buffer;
105   static constexpr std::size_t recl{sizeof buffer};
106   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
107   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
108 
109   int unit{-1};
110   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
111   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
112       << "EndIoStatement() for OpenNewUnit";
113 
114   StaticDescriptor<0> staticDescriptor;
115   Descriptor &desc{staticDescriptor.descriptor()};
116   desc.Establish(TypeCode{CFI_type_int64_t}, recl, &buffer, 0);
117   desc.Check();
118 
119   static constexpr int records{10};
120   for (int j{1}; j <= records; ++j) {
121     // WRITE(UNIT=unit,REC=j) j
122     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
123     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
124     buffer = j;
125     ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
126         << "OutputDescriptor() for Write";
127     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
128         << "EndIoStatement() for Write";
129   }
130 
131   // OPEN(UNIT=unit,STATUS='OLD',CONVERT='SWAP')
132   io = IONAME(BeginOpenUnit)(unit, __FILE__, __LINE__);
133   ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)";
134   ASSERT_TRUE(IONAME(SetConvert)(io, "SWAP", 4)) << "SetConvert(SWAP)";
135   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
136       << "EndIoStatement() for OpenUnit";
137 
138   for (int j{records}; j >= 1; --j) {
139     // READ(UNIT=unit,REC=j) n
140     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
141     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
142     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc))
143         << "InputDescriptor() for Read";
144     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
145         << "EndIoStatement() for Read";
146     ASSERT_EQ(buffer >> 56, j)
147         << "Read back " << (buffer >> 56) << " from direct unformatted record "
148         << j << ", expected " << j << '\n';
149   }
150 
151   // CLOSE(UNIT=unit,STATUS='DELETE')
152   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
153   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
154   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
155       << "EndIoStatement() for Close";
156 }
157 
158 TEST(ExternalIOTests, TestSequentialFixedUnformatted) {
159   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
160   //   FORM='UNFORMATTED',RECL=8,STATUS='SCRATCH')
161   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
162   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
163       << "SetAccess(SEQUENTIAL)";
164   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
165   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
166 
167   std::int64_t buffer;
168   static constexpr std::size_t recl{sizeof buffer};
169 
170   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
171   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
172 
173   int unit{-1};
174   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
175   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
176       << "EndIoStatement() for OpenNewUnit";
177 
178   // INQUIRE(IOLENGTH=) j, ...
179   StaticDescriptor<0> staticDescriptor;
180   Descriptor &desc{staticDescriptor.descriptor()};
181   desc.Establish(TypeCode{CFI_type_int64_t}, recl, &buffer, 0);
182   desc.Dump(stderr);
183   desc.Check();
184   io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
185   for (int j{1}; j <= 3; ++j) {
186     ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
187         << "OutputDescriptor() for InquireIoLength";
188   }
189   ASSERT_EQ(IONAME(GetIoLength)(io), 3 * recl) << "GetIoLength";
190   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
191       << "EndIoStatement() for InquireIoLength";
192 
193   static const int records{10};
194   for (int j{1}; j <= records; ++j) {
195     // DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO
196     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
197     buffer = j;
198     ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
199         << "OutputDescriptor() for Write";
200     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
201         << "EndIoStatement() for WRITE";
202   }
203 
204   // REWIND(UNIT=unit)
205   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
206   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
207       << "EndIoStatement() for Rewind";
208 
209   for (int j{1}; j <= records; ++j) {
210     // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO
211     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
212     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc))
213         << "InputDescriptor() for Read";
214     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
215         << "EndIoStatement() for Read";
216     ASSERT_EQ(buffer, j) << "Read back " << buffer
217                          << " from sequential fixed unformatted record " << j
218                          << ", expected " << j << '\n';
219   }
220 
221   for (int j{records}; j >= 1; --j) {
222     // BACKSPACE(UNIT=unit)
223     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
224     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
225         << "EndIoStatement() for Backspace (before read)";
226     // READ(UNIT=unit) n
227     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
228     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc))
229         << "InputDescriptor() for Read";
230     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
231         << "EndIoStatement() for Read";
232     ASSERT_EQ(buffer, j) << "Read back " << buffer
233                          << " from sequential fixed unformatted record " << j
234                          << " after backspacing, expected " << j << '\n';
235     // BACKSPACE(UNIT=unit)
236     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
237     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
238         << "EndIoStatement() for Backspace (after read)";
239   }
240 
241   // CLOSE(UNIT=unit,STATUS='DELETE')
242   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
243   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
244   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
245       << "EndIoStatement() for Close";
246 }
247 
248 TEST(ExternalIOTests, TestSequentialVariableUnformatted) {
249   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
250   //   FORM='UNFORMATTED',STATUS='SCRATCH')
251   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
252 
253   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
254       << "SetAccess(SEQUENTIAL)";
255   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
256   ASSERT_TRUE(IONAME(SetForm)(io, "UNFORMATTED", 11)) << "SetForm(UNFORMATTED)";
257   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
258 
259   int unit{-1};
260   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
261   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
262       << "EndIoStatement() for OpenNewUnit";
263 
264   static const int records{10};
265   std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)]
266   for (int j{0}; j < records; ++j) {
267     buffer[j] = j;
268   }
269 
270   StaticDescriptor<0> staticDescriptor;
271   Descriptor &desc{staticDescriptor.descriptor()};
272 
273   for (int j{1}; j <= records; ++j) {
274     // DO J=1,RECORDS; WRITE(UNIT=unit) BUFFER(0:j); END DO
275     io = IONAME(BeginUnformattedOutput)(unit, __FILE__, __LINE__);
276     desc.Establish(TypeCode{sizeof *buffer}, j * sizeof *buffer, buffer, 0);
277     desc.Check();
278     ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
279         << "OutputDescriptor() for Write";
280     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
281         << "EndIoStatement() for Write";
282   }
283 
284   // REWIND(UNIT=unit)
285   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
286   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
287       << "EndIoStatement() for Rewind";
288   for (int j{1}; j <= records; ++j) {
289     // DO J=1,RECORDS; READ(UNIT=unit) n; check n; END DO
290     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
291     desc.Establish(TypeCode{sizeof *buffer}, j * sizeof *buffer, buffer, 0);
292     desc.Check();
293     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc))
294         << "InputDescriptor() for Read";
295     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
296         << "EndIoStatement() for Read";
297     for (int k{0}; k < j; ++k) {
298       ASSERT_EQ(buffer[k], k) << "Read back [" << k << "]=" << buffer[k]
299                               << " from direct unformatted record " << j
300                               << ", expected " << k << '\n';
301     }
302   }
303 
304   for (int j{records}; j >= 1; --j) {
305     // BACKSPACE(unit)
306     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
307     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
308         << "EndIoStatement() for Backspace (before read)";
309     // READ(unit=unit) n; check
310     io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
311     desc.Establish(TypeCode{sizeof *buffer}, j * sizeof *buffer, buffer, 0);
312     desc.Check();
313     ASSERT_TRUE(IONAME(InputDescriptor)(io, desc)) << "InputDescriptor()";
314     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
315         << "EndIoStatement() for InputUnformattedBlock";
316     for (int k{0}; k < j; ++k) {
317       ASSERT_EQ(buffer[k], k) << "Read back [" << k << "]=" << buffer[k]
318                               << " from sequential variable unformatted record "
319                               << j << ", expected " << k << '\n';
320     }
321     // BACKSPACE(unit)
322     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
323     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
324         << "EndIoStatement() for Backspace (after read)";
325   }
326 
327   // CLOSE(UNIT=unit,STATUS='DELETE')
328   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
329   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
330   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
331       << "EndIoStatement() for Close";
332 }
333 
334 TEST(ExternalIOTests, TestDirectFormatted) {
335   // OPEN(NEWUNIT=unit,ACCESS='DIRECT',ACTION='READWRITE',&
336   //   FORM='FORMATTED',RECL=8,STATUS='SCRATCH')
337   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
338   ASSERT_TRUE(IONAME(SetAccess)(io, "DIRECT", 6)) << "SetAccess(DIRECT)";
339   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
340   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
341 
342   static constexpr std::size_t recl{8};
343   ASSERT_TRUE(IONAME(SetRecl)(io, recl)) << "SetRecl()";
344   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
345 
346   int unit{-1};
347   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
348   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
349       << "EndIoStatement() for OpenNewUnit";
350 
351   static constexpr int records{10};
352   static const char fmt[]{"(I4)"};
353   for (int j{1}; j <= records; ++j) {
354     // WRITE(UNIT=unit,FMT=fmt,REC=j) j
355     io = IONAME(BeginExternalFormattedOutput)(
356         fmt, sizeof fmt - 1, nullptr, unit, __FILE__, __LINE__);
357     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
358     ASSERT_TRUE(IONAME(OutputInteger64)(io, j)) << "OutputInteger64()";
359     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
360         << "EndIoStatement() for OutputInteger64";
361   }
362 
363   for (int j{records}; j >= 1; --j) {
364     // READ(UNIT=unit,FMT=fmt,REC=j) n
365     io = IONAME(BeginExternalFormattedInput)(
366         fmt, sizeof fmt - 1, nullptr, unit, __FILE__, __LINE__);
367     ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
368     std::int64_t buffer;
369     ASSERT_TRUE(IONAME(InputInteger)(io, buffer)) << "InputInteger()";
370     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
371         << "EndIoStatement() for InputInteger";
372 
373     ASSERT_EQ(buffer, j) << "Read back " << buffer
374                          << " from direct formatted record " << j
375                          << ", expected " << j << '\n';
376   }
377 
378   // CLOSE(UNIT=unit,STATUS='DELETE')
379   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
380   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
381   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
382       << "EndIoStatement() for Close";
383 }
384 
385 TEST(ExternalIOTests, TestSequentialVariableFormatted) {
386   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
387   //   FORM='FORMATTED',STATUS='SCRATCH')
388   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
389   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
390       << "SetAccess(SEQUENTIAL)";
391   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
392   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
393   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
394 
395   int unit{-1};
396   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
397   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
398       << "EndIoStatement() for OpenNewUnit";
399 
400   static const int records{10};
401   std::int64_t buffer[records]; // INTEGER*8 :: BUFFER(0:9) = [(j,j=0,9)]
402   for (int j{0}; j < records; ++j) {
403     buffer[j] = j;
404   }
405 
406   char fmt[32];
407   for (int j{1}; j <= records; ++j) {
408     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
409     // DO J=1,RECORDS; WRITE(UNIT=unit,FMT=fmt) BUFFER(0:j); END DO
410     io = IONAME(BeginExternalFormattedOutput)(
411         fmt, std::strlen(fmt), nullptr, unit, __FILE__, __LINE__);
412     for (int k{0}; k < j; ++k) {
413       ASSERT_TRUE(IONAME(OutputInteger64)(io, buffer[k]))
414           << "OutputInteger64()";
415     }
416     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
417         << "EndIoStatement() for OutputInteger64";
418   }
419 
420   // REWIND(UNIT=unit)
421   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
422   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
423       << "EndIoStatement() for Rewind";
424 
425   for (int j{1}; j <= records; ++j) {
426     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
427     // DO J=1,RECORDS; READ(UNIT=unit,FMT=fmt) n; check n; END DO
428     io = IONAME(BeginExternalFormattedInput)(
429         fmt, std::strlen(fmt), nullptr, unit, __FILE__, __LINE__);
430 
431     std::int64_t check[records];
432     for (int k{0}; k < j; ++k) {
433       ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()";
434     }
435     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
436         << "EndIoStatement() for InputInteger";
437 
438     for (int k{0}; k < j; ++k) {
439       ASSERT_EQ(buffer[k], check[k])
440           << "Read back [" << k << "]=" << check[k]
441           << " from sequential variable formatted record " << j << ", expected "
442           << buffer[k] << '\n';
443     }
444   }
445 
446   for (int j{records}; j >= 1; --j) {
447     // BACKSPACE(unit)
448     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
449     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
450         << "EndIoStatement() for Backspace (before read)";
451 
452     std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
453     // READ(UNIT=unit,FMT=fmt,SIZE=chars) n; check
454     io = IONAME(BeginExternalFormattedInput)(
455         fmt, std::strlen(fmt), nullptr, unit, __FILE__, __LINE__);
456 
457     std::int64_t check[records];
458     for (int k{0}; k < j; ++k) {
459       ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()";
460     }
461 
462     std::size_t chars{IONAME(GetSize)(io)};
463     ASSERT_EQ(chars, j * 4u)
464         << "GetSize()=" << chars << ", expected " << (j * 4u) << '\n';
465     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
466         << "EndIoStatement() for InputInteger";
467     for (int k{0}; k < j; ++k) {
468       ASSERT_EQ(buffer[k], check[k])
469           << "Read back [" << k << "]=" << buffer[k]
470           << " from sequential variable formatted record " << j << ", expected "
471           << buffer[k] << '\n';
472     }
473 
474     // BACKSPACE(unit)
475     io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
476     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
477         << "EndIoStatement() for Backspace (after read)";
478   }
479 
480   // CLOSE(UNIT=unit,STATUS='DELETE')
481   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
482   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
483   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
484       << "EndIoStatement() for Close";
485 }
486 
487 TEST(ExternalIOTests, TestNonAvancingInput) {
488   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
489   //   FORM='FORMATTED',STATUS='SCRATCH')
490   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
491   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
492       << "SetAccess(SEQUENTIAL)";
493   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
494   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
495   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
496 
497   int unit{-1};
498   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
499   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
500       << "EndIoStatement() for OpenNewUnit";
501 
502   // Write the file to be used for the input test.
503   static constexpr std::string_view records[] = {
504       "ABCDEFGH", "IJKLMNOP", "QRSTUVWX"};
505   static constexpr std::string_view fmt{"(A)"};
506   for (const auto &record : records) {
507     // WRITE(UNIT=unit,FMT=fmt) record
508     io = IONAME(BeginExternalFormattedOutput)(
509         fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
510     ASSERT_TRUE(IONAME(OutputAscii)(io, record.data(), record.length()))
511         << "OutputAscii()";
512     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
513         << "EndIoStatement() for OutputAscii";
514   }
515 
516   // REWIND(UNIT=unit)
517   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
518   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
519       << "EndIoStatement() for Rewind";
520 
521   struct TestItems {
522     std::string item;
523     int expectedIoStat;
524     std::string expectedItemValue[2];
525   };
526   // Actual non advancing input IO test
527   TestItems inputItems[]{
528       {std::string(4, '+'), IostatOk, {"ABCD", "ABCD"}},
529       {std::string(4, '+'), IostatOk, {"EFGH", "EFGH"}},
530       {std::string(4, '+'), IostatEor, {"++++", "    "}},
531       {std::string(2, '+'), IostatOk, {"IJ", "IJ"}},
532       {std::string(8, '+'), IostatEor, {"++++++++", "KLMNOP  "}},
533       {std::string(10, '+'), IostatEor, {"++++++++++", "QRSTUVWX  "}},
534   };
535 
536   // Test with PAD='NO'
537   int j{0};
538   for (auto &inputItem : inputItems) {
539     // READ(UNIT=unit, FMT=fmt, ADVANCE='NO', PAD='NO', IOSTAT=iostat) inputItem
540     io = IONAME(BeginExternalFormattedInput)(
541         fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
542     IONAME(EnableHandlers)(io, true, false, false, false, false);
543     ASSERT_TRUE(IONAME(SetAdvance)(io, "NO", 2)) << "SetAdvance(NO)" << j;
544     ASSERT_TRUE(IONAME(SetPad)(io, "NO", 2)) << "SetPad(NO)" << j;
545     bool result{
546         IONAME(InputAscii)(io, inputItem.item.data(), inputItem.item.length())};
547     ASSERT_EQ(result, inputItem.expectedIoStat == IostatOk)
548         << "InputAscii() " << j;
549     ASSERT_EQ(IONAME(EndIoStatement)(io), inputItem.expectedIoStat)
550         << "EndIoStatement() for Read " << j;
551     ASSERT_EQ(inputItem.item, inputItem.expectedItemValue[0])
552         << "Input-item value after non advancing read " << j;
553     j++;
554   }
555 
556   // REWIND(UNIT=unit)
557   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
558   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
559       << "EndIoStatement() for Rewind";
560 
561   // Test again with PAD='YES'
562   j = 0;
563   for (auto &inputItem : inputItems) {
564     // READ(UNIT=unit, FMT=fmt, ADVANCE='NO', PAD='YES', IOSTAT=iostat)
565     // inputItem
566     io = IONAME(BeginExternalFormattedInput)(
567         fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
568     IONAME(EnableHandlers)(io, true, false, false, false, false);
569     ASSERT_TRUE(IONAME(SetAdvance)(io, "NO", 2)) << "SetAdvance(NO)" << j;
570     ASSERT_TRUE(IONAME(SetPad)(io, "YES", 3)) << "SetPad(YES)" << j;
571     bool result{
572         IONAME(InputAscii)(io, inputItem.item.data(), inputItem.item.length())};
573     ASSERT_EQ(result, inputItem.expectedIoStat == IostatOk)
574         << "InputAscii() " << j;
575     ASSERT_EQ(IONAME(EndIoStatement)(io), inputItem.expectedIoStat)
576         << "EndIoStatement() for Read " << j;
577     ASSERT_EQ(inputItem.item, inputItem.expectedItemValue[1])
578         << "Input-item value after non advancing read " << j;
579     j++;
580   }
581 
582   // CLOSE(UNIT=unit)
583   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
584   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
585       << "EndIoStatement() for Close";
586 }
587 
588 TEST(ExternalIOTests, TestWriteAfterNonAvancingInput) {
589   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
590   //   FORM='FORMATTED',STATUS='SCRATCH')
591   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
592   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
593       << "SetAccess(SEQUENTIAL)";
594   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
595   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
596   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
597 
598   int unit{-1};
599   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
600   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
601       << "EndIoStatement() for OpenNewUnit";
602 
603   // Write the file to be used for the input test.
604   static constexpr std::string_view records[] = {"ABCDEFGHIJKLMNOPQRST"};
605   static constexpr std::string_view fmt{"(A)"};
606   for (const auto &record : records) {
607     // WRITE(UNIT=unit,FMT=fmt) record
608     io = IONAME(BeginExternalFormattedOutput)(
609         fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
610     ASSERT_TRUE(IONAME(OutputAscii)(io, record.data(), record.length()))
611         << "OutputAscii()";
612     ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
613         << "EndIoStatement() for OutputAscii";
614   }
615 
616   // REWIND(UNIT=unit)
617   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
618   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
619       << "EndIoStatement() for Rewind";
620 
621   struct TestItems {
622     std::string item;
623     int expectedIoStat;
624     std::string expectedItemValue;
625   };
626   // Actual non advancing input IO test
627   TestItems inputItems[]{
628       {std::string(4, '+'), IostatOk, "ABCD"},
629       {std::string(4, '+'), IostatOk, "EFGH"},
630   };
631 
632   int j{0};
633   for (auto &inputItem : inputItems) {
634     // READ(UNIT=unit, FMT=fmt, ADVANCE='NO', IOSTAT=iostat) inputItem
635     io = IONAME(BeginExternalFormattedInput)(
636         fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
637     IONAME(EnableHandlers)(io, true, false, false, false, false);
638     ASSERT_TRUE(IONAME(SetAdvance)(io, "NO", 2)) << "SetAdvance(NO)" << j;
639     ASSERT_TRUE(
640         IONAME(InputAscii)(io, inputItem.item.data(), inputItem.item.length()))
641         << "InputAscii() " << j;
642     ASSERT_EQ(IONAME(EndIoStatement)(io), inputItem.expectedIoStat)
643         << "EndIoStatement() for Read " << j;
644     ASSERT_EQ(inputItem.item, inputItem.expectedItemValue)
645         << "Input-item value after non advancing read " << j;
646     j++;
647   }
648 
649   // WRITE(UNIT=unit, FMT=fmt, IOSTAT=iostat) outputItem.
650   static constexpr std::string_view outputItem{"XYZ"};
651   // WRITE(UNIT=unit,FMT=fmt) record
652   io = IONAME(BeginExternalFormattedOutput)(
653       fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
654   ASSERT_TRUE(IONAME(OutputAscii)(io, outputItem.data(), outputItem.length()))
655       << "OutputAscii()";
656   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
657       << "EndIoStatement() for OutputAscii";
658 
659   // Verify that the output was written in the record read in non advancing
660   // mode, after the read part, and that the end was truncated.
661 
662   // REWIND(UNIT=unit)
663   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
664   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
665       << "EndIoStatement() for Rewind";
666 
667   std::string resultRecord(20, '+');
668   std::string expectedRecord{"ABCDEFGHXYZ         "};
669   // READ(UNIT=unit, FMT=fmt, IOSTAT=iostat) result
670   io = IONAME(BeginExternalFormattedInput)(
671       fmt.data(), fmt.length(), nullptr, unit, __FILE__, __LINE__);
672   IONAME(EnableHandlers)(io, true, false, false, false, false);
673   ASSERT_TRUE(
674       IONAME(InputAscii)(io, resultRecord.data(), resultRecord.length()))
675       << "InputAscii() ";
676   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
677       << "EndIoStatement() for Read ";
678   ASSERT_EQ(resultRecord, expectedRecord)
679       << "Record after non advancing read followed by write";
680   // CLOSE(UNIT=unit)
681   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
682   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
683       << "EndIoStatement() for Close";
684 }
685 
686 TEST(ExternalIOTests, TestWriteAfterEndfile) {
687   // OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
688   //   FORM='FORMATTED',STATUS='SCRATCH')
689   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
690   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
691       << "SetAccess(SEQUENTIAL)";
692   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
693   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
694   ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
695   int unit{-1};
696   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
697   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
698       << "EndIoStatement() for OpenNewUnit";
699   // WRITE(unit,"(I8)") 1234
700   static constexpr std::string_view format{"(I8)"};
701   io = IONAME(BeginExternalFormattedOutput)(
702       format.data(), format.length(), nullptr, unit, __FILE__, __LINE__);
703   ASSERT_TRUE(IONAME(OutputInteger64)(io, 1234)) << "OutputInteger64()";
704   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
705       << "EndIoStatement for WRITE before ENDFILE";
706   // ENDFILE(unit)
707   io = IONAME(BeginEndfile)(unit, __FILE__, __LINE__);
708   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
709       << "EndIoStatement for ENDFILE";
710   // WRITE(unit,"(I8)",iostat=iostat) 5678
711   io = IONAME(BeginExternalFormattedOutput)(
712       format.data(), format.length(), nullptr, unit, __FILE__, __LINE__);
713   IONAME(EnableHandlers)(io, true /*IOSTAT=*/);
714   ASSERT_FALSE(IONAME(OutputInteger64)(io, 5678)) << "OutputInteger64()";
715   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatWriteAfterEndfile)
716       << "EndIoStatement for WRITE after ENDFILE";
717   // BACKSPACE(unit)
718   io = IONAME(BeginBackspace)(unit, __FILE__, __LINE__);
719   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
720       << "EndIoStatement for BACKSPACE";
721   // WRITE(unit,"(I8)") 3456
722   io = IONAME(BeginExternalFormattedOutput)(
723       format.data(), format.length(), nullptr, unit, __FILE__, __LINE__);
724   ASSERT_TRUE(IONAME(OutputInteger64)(io, 3456)) << "OutputInteger64()";
725   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
726       << "EndIoStatement for WRITE after BACKSPACE";
727   // REWIND(unit)
728   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
729   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
730       << "EndIoStatement for REWIND";
731   // READ(unit,"(I8)",END=) j, k
732   std::int64_t j{-1}, k{-1}, eof{-1};
733   io = IONAME(BeginExternalFormattedInput)(
734       format.data(), format.length(), nullptr, unit, __FILE__, __LINE__);
735   IONAME(EnableHandlers)(io, false, false, true /*END=*/);
736   ASSERT_TRUE(IONAME(InputInteger)(io, j)) << "InputInteger(j)";
737   ASSERT_EQ(j, 1234) << "READ(j)";
738   ASSERT_TRUE(IONAME(InputInteger)(io, k)) << "InputInteger(k)";
739   ASSERT_EQ(k, 3456) << "READ(k)";
740   ASSERT_FALSE(IONAME(InputInteger)(io, eof)) << "InputInteger(eof)";
741   ASSERT_EQ(eof, -1) << "READ(eof)";
742   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatEnd) << "EndIoStatement for READ";
743   // CLOSE(UNIT=unit)
744   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
745   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
746       << "EndIoStatement() for Close";
747 }
748 
749 TEST(ExternalIOTests, TestUTF8Encoding) {
750   // OPEN(FILE="utf8test",NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
751   //   FORM='FORMATTED',STATUS='REPLACE',ENCODING='UTF-8')
752   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
753   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
754       << "SetAccess(SEQUENTIAL)";
755   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
756   ASSERT_TRUE(IONAME(SetFile)(io, "utf8test", 8)) << "SetFile(utf8test)";
757   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
758   ASSERT_TRUE(IONAME(SetStatus)(io, "REPLACE", 7)) << "SetStatus(REPLACE)";
759   ASSERT_TRUE(IONAME(SetEncoding)(io, "UTF-8", 5)) << "SetEncoding(UTF-8)";
760   int unit{-1};
761   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
762   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
763       << "EndIoStatement() for first OPEN";
764   char buffer[12];
765   std::memcpy(buffer,
766       "abc\x80\xff"
767       "de\0\0\0\0\0",
768       12);
769   // WRITE(unit, *) buffer
770   io = IONAME(BeginExternalListOutput)(unit, __FILE__, __LINE__);
771   StaticDescriptor<0> staticDescriptor;
772   Descriptor &desc{staticDescriptor.descriptor()};
773   desc.Establish(TypeCode{CFI_type_char}, 7, buffer, 0);
774   desc.Check();
775   ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc));
776   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
777       << "EndIoStatement() for WRITE";
778   // REWIND(unit)
779   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
780   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
781       << "EndIoStatement for REWIND";
782   // READ(unit, *) buffer
783   desc.Establish(TypeCode(CFI_type_char), sizeof buffer, buffer, 0);
784   desc.Check();
785   io = IONAME(BeginExternalListInput)(unit, __FILE__, __LINE__);
786   ASSERT_TRUE(IONAME(InputDescriptor)(io, desc));
787   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
788       << "EndIoStatement() for first READ";
789   ASSERT_EQ(std::memcmp(buffer,
790                 "abc\x80\xff"
791                 "de     ",
792                 12),
793       0);
794   // CLOSE(UNIT=unit,STATUS='KEEP')
795   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
796   ASSERT_TRUE(IONAME(SetStatus)(io, "KEEP", 4)) << "SetStatus(KEEP)";
797   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
798       << "EndIoStatement() for first CLOSE";
799   // OPEN(FILE="utf8test",NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
800   //   FORM='FORMATTED',STATUS='OLD')
801   io = IONAME(BeginOpenNewUnit)(__FILE__, __LINE__);
802   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
803       << "SetAccess(SEQUENTIAL)";
804   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
805   ASSERT_TRUE(IONAME(SetFile)(io, "utf8test", 8)) << "SetFile(utf8test)";
806   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
807   ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)";
808   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
809   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
810       << "EndIoStatement() for second OPEN";
811   // READ(unit, *) buffer
812   io = IONAME(BeginExternalListInput)(unit, __FILE__, __LINE__);
813   ASSERT_TRUE(IONAME(InputDescriptor)(io, desc));
814   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
815       << "EndIoStatement() for second READ";
816   ASSERT_EQ(std::memcmp(buffer,
817                 "abc\xc2\x80\xc3\xbf"
818                 "de   ",
819                 12),
820       0);
821   // CLOSE(UNIT=unit,STATUS='DELETE')
822   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
823   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
824   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
825       << "EndIoStatement() for second CLOSE";
826 }
827 
828 TEST(ExternalIOTests, TestUCS) {
829   // OPEN(FILE="ucstest',NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
830   //   FORM='FORMATTED',STATUS='REPLACE',ENCODING='UTF-8')
831   auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
832   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
833       << "SetAccess(SEQUENTIAL)";
834   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
835   ASSERT_TRUE(IONAME(SetFile)(io, "ucstest", 7)) << "SetAction(ucstest)";
836   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
837   ASSERT_TRUE(IONAME(SetStatus)(io, "REPLACE", 7)) << "SetStatus(REPLACE)";
838   ASSERT_TRUE(IONAME(SetEncoding)(io, "UTF-8", 5)) << "SetEncoding(UTF-8)";
839   int unit{-1};
840   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
841   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
842       << "EndIoStatement() for first OPEN";
843   char32_t wbuffer[8]{U"abc\u0080\uffff"
844                       "de"};
845   // WRITE(unit, *) wbuffec
846   io = IONAME(BeginExternalListOutput)(unit, __FILE__, __LINE__);
847   StaticDescriptor<0> staticDescriptor;
848   Descriptor &desc{staticDescriptor.descriptor()};
849   desc.Establish(TypeCode{CFI_type_char32_t}, sizeof wbuffer - sizeof(char32_t),
850       wbuffer, 0);
851   desc.Check();
852   ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc));
853   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
854       << "EndIoStatement() for WRITE";
855   // REWIND(unit)
856   io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
857   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
858       << "EndIoStatement for REWIND";
859   // READ(unit, *) buffer
860   io = IONAME(BeginExternalListInput)(unit, __FILE__, __LINE__);
861   desc.Establish(TypeCode{CFI_type_char32_t}, sizeof wbuffer, wbuffer, 0);
862   desc.Check();
863   ASSERT_TRUE(IONAME(InputDescriptor)(io, desc));
864   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
865       << "EndIoStatement() for first READ";
866   char dump[80];
867   dump[0] = '\0';
868   for (int j{0}; j < 8; ++j) {
869     std::size_t dumpLen{std::strlen(dump)};
870     std::snprintf(
871         dump + dumpLen, sizeof dump - dumpLen, " %x", (unsigned)wbuffer[j]);
872   }
873   EXPECT_EQ(wbuffer[0], U'a') << dump;
874   EXPECT_EQ(wbuffer[1], U'b') << dump;
875   EXPECT_EQ(wbuffer[2], U'c') << dump;
876   EXPECT_EQ(wbuffer[3], U'\u0080') << dump;
877   EXPECT_EQ(wbuffer[4], U'\uffff') << dump;
878   EXPECT_EQ(wbuffer[5], U'd') << dump;
879   EXPECT_EQ(wbuffer[6], U'e') << dump;
880   EXPECT_EQ(wbuffer[7], U' ') << dump;
881   // CLOSE(UNIT=unit,STATUS='KEEP')
882   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
883   ASSERT_TRUE(IONAME(SetStatus)(io, "KEEP", 4)) << "SetStatus(KEEP)";
884   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
885       << "EndIoStatement() for first CLOSE";
886   // OPEN(FILE="ucstest",NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
887   //   FORM='FORMATTED',STATUS='OLD')
888   io = IONAME(BeginOpenNewUnit)(__FILE__, __LINE__);
889   ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
890       << "SetAccess(SEQUENTIAL)";
891   ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
892   ASSERT_TRUE(IONAME(SetFile)(io, "ucstest", 7)) << "SetFile(ucstest)";
893   ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
894   ASSERT_TRUE(IONAME(SetStatus)(io, "OLD", 3)) << "SetStatus(OLD)";
895   ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
896   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
897       << "EndIoStatement() for second OPEN";
898   char buffer[12];
899   // READ(unit, *) buffer
900   io = IONAME(BeginExternalListInput)(unit, __FILE__, __LINE__);
901   desc.Establish(TypeCode{CFI_type_char}, sizeof buffer, buffer, 0);
902   desc.Check();
903   ASSERT_TRUE(IONAME(InputDescriptor)(io, desc));
904   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
905       << "EndIoStatement() for second READ";
906   dump[0] = '\0';
907   for (int j{0}; j < 12; ++j) {
908     std::size_t dumpLen{std::strlen(dump)};
909     std::snprintf(dump + dumpLen, sizeof dump - dumpLen, " %x",
910         (unsigned)(unsigned char)buffer[j]);
911   }
912   EXPECT_EQ(std::memcmp(buffer,
913                 "abc\xc2\x80\xef\xbf\xbf"
914                 "de  ",
915                 12),
916       0)
917       << dump;
918   // CLOSE(UNIT=unit,STATUS='DELETE')
919   io = IONAME(BeginClose)(unit, __FILE__, __LINE__);
920   ASSERT_TRUE(IONAME(SetStatus)(io, "DELETE", 6)) << "SetStatus(DELETE)";
921   ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
922       << "EndIoStatement() for second CLOSE";
923 }
924 
925 TEST(ExternalIOTests, BigUnitNumbers) {
926   if (std::numeric_limits<ExternalUnit>::max() <
927       std::numeric_limits<std::int64_t>::max()) {
928     std::int64_t unit64Ok = std::numeric_limits<ExternalUnit>::max();
929     std::int64_t unit64Bad = unit64Ok + 1;
930     std::int64_t unit64Bad2 =
931         static_cast<std::int64_t>(std::numeric_limits<ExternalUnit>::min()) - 1;
932     EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, true), IostatOk);
933     EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, false), IostatOk);
934     EXPECT_EQ(
935         IONAME(CheckUnitNumberInRange64)(unit64Bad, true), IostatUnitOverflow);
936     EXPECT_EQ(
937         IONAME(CheckUnitNumberInRange64)(unit64Bad2, true), IostatUnitOverflow);
938     constexpr std::size_t n{80};
939     char expectedMsg[n + 1];
940     expectedMsg[n] = '\0';
941     std::snprintf(expectedMsg, n, "UNIT number %jd is out of range",
942         static_cast<std::intmax_t>(unit64Bad));
943     EXPECT_DEATH(
944         IONAME(CheckUnitNumberInRange64)(unit64Bad, false), expectedMsg);
945     for (auto i{std::strlen(expectedMsg)}; i < n; ++i) {
946       expectedMsg[i] = ' ';
947     }
948     char msg[n + 1];
949     msg[n] = '\0';
950     EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Bad, true, msg, n),
951         IostatUnitOverflow);
952     EXPECT_EQ(std::strncmp(msg, expectedMsg, n), 0);
953   }
954 }
955