1 //
2 // Copyright (c) 2015 Artyom Beilis (Tonkikh)
3 // Copyright (c) 2019 Alexander Grund
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See
6 // accompanying file LICENSE or copy at
7 // http://www.boost.org/LICENSE_1_0.txt)
8 //
9
10 #include <boost/nowide/fstream.hpp>
11
12 #include <boost/nowide/convert.hpp>
13 #include <boost/nowide/cstdio.hpp>
14 #include <fstream>
15 #include <iostream>
16 #include <string>
17
18 #include "test.hpp"
19
20 namespace nw = boost::nowide;
21
make_empty_file(const char * filepath)22 void make_empty_file(const char* filepath)
23 {
24 nw::ofstream f(filepath, std::ios_base::out | std::ios::trunc);
25 TEST(f);
26 }
27
file_exists(const char * filepath)28 bool file_exists(const char* filepath)
29 {
30 FILE* f = nw::fopen(filepath, "r");
31 if(f)
32 {
33 std::fclose(f);
34 return true;
35 } else
36 return false;
37 }
38
read_file(const char * filepath,bool binary_mode=false)39 std::string read_file(const char* filepath, bool binary_mode = false)
40 {
41 FILE* f = nw::fopen(filepath, binary_mode ? "rb" : "r");
42 TEST(f);
43 std::string content;
44 int c;
45 while((c = std::fgetc(f)) != EOF)
46 content.push_back(static_cast<char>(c));
47 std::fclose(f);
48 return content;
49 }
50
test_with_different_buffer_sizes(const char * filepath)51 void test_with_different_buffer_sizes(const char* filepath)
52 {
53 /* Important part of the standard for mixing input with output:
54 However, output shall not be directly followed by input without an intervening call to the fflush function
55 or to a file positioning function (fseek, fsetpos, or rewind),
56 and input shall not be directly followed by output without an intervening call to a file positioning function,
57 unless the input operation encounters end-of-file.
58 */
59 for(int i = -1; i < 16; i++)
60 {
61 std::cout << "Buffer size = " << i << std::endl;
62 char buf[16];
63 nw::fstream f;
64 // Different conditions when setbuf might be called: Usually before opening a file is OK
65 if(i >= 0)
66 f.rdbuf()->pubsetbuf((i == 0) ? NULL : buf, i);
67 f.open(filepath, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
68 TEST(f);
69 // Add 'abcdefg'
70 TEST(f.put('a'));
71 TEST(f.put('b'));
72 TEST(f.put('c'));
73 TEST(f.put('d'));
74 TEST(f.put('e'));
75 TEST(f.put('f'));
76 TEST(f.put('g'));
77 // Read first char
78 TEST(f.seekg(0));
79 TEST(f.get() == 'a');
80 TEST(f.gcount() == 1u);
81 // Skip next char
82 TEST(f.seekg(1, std::ios::cur));
83 TEST(f.get() == 'c');
84 TEST(f.gcount() == 1u);
85 // Go back 1 char
86 TEST(f.seekg(-1, std::ios::cur));
87 TEST(f.get() == 'c');
88 TEST(f.gcount() == 1u);
89
90 // Test switching between read->write->read
91 // case 1) overwrite, flush, read
92 TEST(f.seekg(1));
93 TEST(f.put('B'));
94 TEST(f.flush()); // Flush when changing out->in
95 TEST(f.get() == 'c');
96 TEST(f.gcount() == 1u);
97 TEST(f.seekg(1));
98 TEST(f.get() == 'B');
99 TEST(f.gcount() == 1u);
100 // case 2) overwrite, seek, read
101 TEST(f.seekg(2));
102 TEST(f.put('C'));
103 TEST(f.seekg(3)); // Seek when changing out->in
104 TEST(f.get() == 'd');
105 TEST(f.gcount() == 1u);
106
107 // Check that sequence from start equals expected
108 TEST(f.seekg(0));
109 TEST(f.get() == 'a');
110 TEST(f.get() == 'B');
111 TEST(f.get() == 'C');
112 TEST(f.get() == 'd');
113 TEST(f.get() == 'e');
114
115 // Putback after flush is implementation defined
116 // Boost.Nowide: Works
117 #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
118 TEST(f << std::flush);
119 TEST(f.putback('e'));
120 TEST(f.putback('d'));
121 TEST(f.get() == 'd');
122 TEST(f.get() == 'e');
123 #endif
124 // Rest of sequence
125 TEST(f.get() == 'f');
126 TEST(f.get() == 'g');
127 TEST(f.get() == EOF);
128
129 // Put back until front of file is reached
130 f.clear();
131 TEST(f.seekg(1));
132 TEST(f.get() == 'B');
133 TEST(f.putback('B'));
134 // Putting back multiple chars is not possible on all implementations after a seek/flush
135 #if BOOST_NOWIDE_USE_FILEBUF_REPLACEMENT
136 TEST(f.putback('a'));
137 TEST(!f.putback('x')); // At beginning of file -> No putback possible
138 // Get characters that were putback to avoid MSVC bug https://github.com/microsoft/STL/issues/342
139 f.clear();
140 TEST(f.get() == 'a');
141 #endif
142 TEST(f.get() == 'B');
143 f.close();
144 TEST(nw::remove(filepath) == 0);
145 }
146 }
147
test_close(const char * filepath)148 void test_close(const char* filepath)
149 {
150 const std::string filepath2 = std::string(filepath) + ".2";
151 // Make sure file does not exist yet
152 TEST(!file_exists(filepath2.c_str()) || nw::remove(filepath2.c_str()) == 0);
153 TEST(!file_exists(filepath2.c_str()));
154 nw::filebuf buf;
155 TEST(buf.open(filepath, std::ios_base::out) == &buf);
156 TEST(buf.is_open());
157 // Opening when already open fails
158 TEST(buf.open(filepath2.c_str(), std::ios_base::out) == NULL);
159 // Still open
160 TEST(buf.is_open());
161 TEST(buf.close() == &buf);
162 // Failed opening did not create file
163 TEST(!file_exists(filepath2.c_str()));
164 // But it should work now:
165 TEST(buf.open(filepath2.c_str(), std::ios_base::out) == &buf);
166 TEST(buf.close() == &buf);
167 TEST(file_exists(filepath2.c_str()));
168 TEST(nw::remove(filepath) == 0);
169 TEST(nw::remove(filepath2.c_str()) == 0);
170 }
171
172 template<typename IFStream, typename OFStream>
test_flush(const char * filepath)173 void test_flush(const char* filepath)
174 {
175 OFStream fo(filepath, std::ios_base::out | std::ios::trunc);
176 TEST(fo);
177 std::string curValue;
178 for(int repeat = 0; repeat < 2; repeat++)
179 {
180 for(size_t len = 1; len <= 1024; len *= 2)
181 {
182 char c = static_cast<char>(len % 13 + repeat + 'a'); // semi-random char
183 std::string input(len, c);
184 fo << input;
185 curValue += input;
186 TEST(fo.flush());
187 std::string s;
188 // Note: Flush on read area is implementation defined, so check whole file instead
189 IFStream fi(filepath);
190 TEST(fi >> s);
191 // coverity[tainted_data]
192 TEST(s == curValue);
193 }
194 }
195 }
196
test_ofstream_creates_file(const char * filename)197 void test_ofstream_creates_file(const char* filename)
198 {
199 TEST(!file_exists(filename) || nw::remove(filename) == 0);
200 TEST(!file_exists(filename));
201 // Ctor
202 {
203 nw::ofstream fo(filename);
204 TEST(fo);
205 }
206 TEST(file_exists(filename));
207 TEST(read_file(filename).empty());
208 TEST(nw::remove(filename) == 0);
209 // Open
210 {
211 nw::ofstream fo;
212 fo.open(filename);
213 TEST(fo);
214 }
215 TEST(file_exists(filename));
216 TEST(read_file(filename).empty());
217 TEST(nw::remove(filename) == 0);
218 }
219
220 // Create filename file with content "test\n"
test_ofstream_write(const char * filename)221 void test_ofstream_write(const char* filename)
222 {
223 // char* ctor
224 {
225 nw::ofstream fo(filename);
226 TEST(fo << "test" << 2 << std::endl);
227 }
228 // char* open
229 TEST(read_file(filename) == "test2\n");
230 TEST(nw::remove(filename) == 0);
231 {
232 nw::ofstream fo;
233 fo.open(filename);
234 TEST(fo << "test" << 2 << std::endl);
235 }
236 TEST(read_file(filename) == "test2\n");
237 TEST(nw::remove(filename) == 0);
238 // string ctor
239 {
240 std::string name = filename;
241 nw::ofstream fo(name);
242 TEST(fo << "test" << 2 << std::endl);
243 }
244 TEST(read_file(filename) == "test2\n");
245 TEST(nw::remove(filename) == 0);
246 // string open
247 {
248 nw::ofstream fo;
249 fo.open(std::string(filename));
250 TEST(fo << "test" << 2 << std::endl);
251 }
252 TEST(read_file(filename) == "test2\n");
253 TEST(nw::remove(filename) == 0);
254 // Binary mode
255 {
256 nw::ofstream fo(filename, std::ios::binary);
257 TEST(fo << "test" << 2 << std::endl);
258 }
259 TEST(read_file(filename, true) == "test2\n");
260 TEST(nw::remove(filename) == 0);
261 // At end
262 {
263 {
264 nw::ofstream fo(filename);
265 TEST(fo << "test" << 2 << std::endl);
266 }
267 nw::ofstream fo(filename, std::ios::ate | std::ios::in);
268 fo << "second" << 2 << std::endl;
269 }
270 TEST(read_file(filename) == "test2\nsecond2\n");
271 TEST(nw::remove(filename) == 0);
272 }
273
test_ifstream_open_read(const char * filename)274 void test_ifstream_open_read(const char* filename)
275 {
276 // Create test file
277 {
278 nw::ofstream fo(filename);
279 TEST(fo << "test" << std::endl);
280 }
281
282 // char* Ctor
283 {
284 nw::ifstream fi(filename);
285 TEST(fi);
286 std::string tmp;
287 TEST(fi >> tmp);
288 TEST(tmp == "test");
289 }
290 // char* open
291 {
292 nw::ifstream fi;
293 fi.open(filename);
294 TEST(fi);
295 std::string tmp;
296 TEST(fi >> tmp);
297 TEST(tmp == "test");
298 }
299 // string ctor
300 {
301 std::string name = filename;
302 nw::ifstream fi(name);
303 TEST(fi);
304 std::string tmp;
305 TEST(fi >> tmp);
306 TEST(tmp == "test");
307 }
308 // string open
309 {
310 nw::ifstream fi;
311 fi.open(std::string(filename));
312 TEST(fi);
313 std::string tmp;
314 TEST(fi >> tmp);
315 TEST(tmp == "test");
316 }
317 // Binary mode
318 {
319 nw::ifstream fi(filename, std::ios::binary);
320 TEST(fi);
321 std::string tmp;
322 TEST(fi >> tmp);
323 TEST(tmp == "test");
324 }
325 // At end
326 {
327 // Need binary file or position check might be throw off by newline conversion
328 {
329 nw::ofstream fo(filename, nw::fstream::binary);
330 TEST(fo << "test");
331 }
332 nw::ifstream fi(filename, nw::fstream::ate | nw::fstream::binary);
333 TEST(fi);
334 TEST(fi.tellg() == std::streampos(4));
335 fi.seekg(-2, std::ios_base::cur);
336 std::string tmp;
337 TEST(fi >> tmp);
338 TEST(tmp == "st");
339 }
340 // Fail on non-existing file
341 TEST(nw::remove(filename) == 0);
342 {
343 nw::ifstream fi(filename);
344 TEST(!fi);
345 }
346 }
347
test_fstream(const char * filename)348 void test_fstream(const char* filename)
349 {
350 const std::string sFilename = filename;
351 TEST(!file_exists(filename) || nw::remove(filename) == 0);
352 TEST(!file_exists(filename));
353 // Fail on non-existing file
354 {
355 nw::fstream f(filename);
356 TEST(!f);
357 nw::fstream f2(sFilename);
358 TEST(!f2);
359 }
360 {
361 nw::fstream f;
362 f.open(filename);
363 TEST(!f);
364 f.open(sFilename);
365 TEST(!f);
366 }
367 TEST(!file_exists(filename));
368 // Create empty file (Ctor)
369 {
370 nw::fstream f(filename, std::ios::out);
371 TEST(f);
372 }
373 TEST(read_file(filename).empty());
374 // Char* ctor
375 {
376 nw::fstream f(filename);
377 TEST(f);
378 TEST(f << "test");
379 std::string tmp;
380 TEST(f.seekg(0));
381 TEST(f >> tmp);
382 TEST(tmp == "test");
383 }
384 TEST(read_file(filename) == "test");
385 // String ctor
386 {
387 nw::fstream f(sFilename);
388 TEST(f);
389 TEST(f << "string_ctor");
390 std::string tmp;
391 TEST(f.seekg(0));
392 TEST(f >> tmp);
393 TEST(tmp == "string_ctor");
394 }
395 TEST(read_file(filename) == "string_ctor");
396 TEST(nw::remove(filename) == 0);
397 // Create empty file (open)
398 {
399 nw::fstream f;
400 f.open(filename, std::ios::out);
401 TEST(f);
402 }
403 TEST(read_file(filename).empty());
404 // Open
405 {
406 nw::fstream f;
407 f.open(filename);
408 TEST(f);
409 TEST(f << "test");
410 std::string tmp;
411 TEST(f.seekg(0));
412 TEST(f >> tmp);
413 TEST(tmp == "test");
414 }
415 TEST(read_file(filename) == "test");
416 // Ctor existing file
417 {
418 nw::fstream f(filename);
419 TEST(f);
420 std::string tmp;
421 TEST(f >> tmp);
422 TEST(tmp == "test");
423 TEST(f.eof());
424 f.clear();
425 TEST(f << "second");
426 }
427 TEST(read_file(filename) == "testsecond");
428 // Trunc & binary
429 {
430 nw::fstream f(filename, std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary);
431 TEST(f);
432 TEST(f << "test2");
433 std::string tmp;
434 TEST(f.seekg(0));
435 TEST(f >> tmp);
436 TEST(tmp == "test2");
437 }
438 TEST(read_file(filename) == "test2");
439 // Reading in write mode fails (existing file!)
440 {
441 nw::fstream f(filename, std::ios::out);
442 std::string tmp;
443 TEST(!(f >> tmp));
444 f.clear();
445 TEST(f << "foo");
446 TEST(f.seekg(0));
447 TEST(!(f >> tmp));
448 }
449 TEST(read_file(filename) == "foo");
450 // Writing in read mode fails (existing file!)
451 {
452 nw::fstream f(filename, std::ios::in);
453 TEST(!(f << "bar"));
454 f.clear();
455 std::string tmp;
456 TEST(f >> tmp);
457 TEST(tmp == "foo");
458 }
459 TEST(read_file(filename) == "foo");
460 TEST(nw::remove(filename) == 0);
461 }
462
463 template<typename T>
is_open(T & stream)464 bool is_open(T& stream)
465 {
466 // There are const and non const versions of is_open, so test both
467 TEST(stream.is_open() == const_cast<const T&>(stream).is_open());
468 return stream.is_open();
469 }
470
471 template<typename T>
do_test_is_open(const char * filename)472 void do_test_is_open(const char* filename)
473 {
474 T f;
475 TEST(!is_open(f));
476 f.open(filename);
477 TEST(f);
478 TEST(is_open(f));
479 f.close();
480 TEST(f);
481 TEST(!is_open(f));
482 }
483
test_is_open(const char * filename)484 void test_is_open(const char* filename)
485 {
486 // Note the order: Output before input so file exists
487 do_test_is_open<nw::ofstream>(filename);
488 do_test_is_open<nw::ifstream>(filename);
489 do_test_is_open<nw::fstream>(filename);
490 TEST(nw::remove(filename) == 0);
491 }
492
test_main(int,char ** argv,char **)493 void test_main(int, char** argv, char**)
494 {
495 const std::string exampleFilename = std::string(argv[0]) + "-\xd7\xa9-\xd0\xbc-\xce\xbd.txt";
496 std::cout << "Testing fstream" << std::endl;
497 test_ofstream_creates_file(exampleFilename.c_str());
498 test_ofstream_write(exampleFilename.c_str());
499 test_ifstream_open_read(exampleFilename.c_str());
500 test_fstream(exampleFilename.c_str());
501 test_is_open(exampleFilename.c_str());
502
503 std::cout << "Complex IO" << std::endl;
504 test_with_different_buffer_sizes(exampleFilename.c_str());
505
506 std::cout << "filebuf::close" << std::endl;
507 test_close(exampleFilename.c_str());
508
509 std::cout << "Flush - Sanity Check" << std::endl;
510 test_flush<std::ifstream, std::ofstream>(exampleFilename.c_str());
511 std::cout << "Flush - Test" << std::endl;
512 test_flush<nw::ifstream, nw::ofstream>(exampleFilename.c_str());
513 }
514