• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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