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