Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
parse_csv_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
50#include <gtest/gtest.h>
51#include <sstream>
52#include <fstream>
53#include <cstdio>
54#include <filesystem>
55#include <parse-csv.H>
56#if defined(_WIN32)
57# include <process.h>
58#else
59# include <unistd.h>
60#endif
61
62using namespace Aleph;
63
64namespace
65{
73long long process_id() noexcept
74{
75#if defined(_WIN32)
76 return static_cast<long long>(_getpid());
77#else
78 return static_cast<long long>(getpid());
79#endif
80}
81} // namespace
82
83//============================================================================
84// csv_read_row Tests - Stream Input
85//============================================================================
86
87class CsvReadRowStreamTest : public ::testing::Test {};
88
90{
91 std::istringstream ss("a,b,c\n");
93
94 ASSERT_EQ(row.size(), 3u);
95 EXPECT_EQ(row(0), "a");
96 EXPECT_EQ(row(1), "b");
97 EXPECT_EQ(row(2), "c");
98}
99
101{
102 std::istringstream ss("hello\n");
104
105 ASSERT_EQ(row.size(), 1u);
106 EXPECT_EQ(row(0), "hello");
107}
108
110{
111 std::istringstream ss(",b,,d\n");
113
114 ASSERT_EQ(row.size(), 4u);
115 EXPECT_EQ(row(0), "");
116 EXPECT_EQ(row(1), "b");
117 EXPECT_EQ(row(2), "");
118 EXPECT_EQ(row(3), "d");
119}
120
122{
123 std::istringstream ss("a,\"hello world\",c\n");
125
126 ASSERT_EQ(row.size(), 3u);
127 EXPECT_EQ(row(0), "a");
128 EXPECT_EQ(row(1), "hello world");
129 EXPECT_EQ(row(2), "c");
130}
131
133{
134 std::istringstream ss("a,\"hello, world\",c\n");
136
137 ASSERT_EQ(row.size(), 3u);
138 EXPECT_EQ(row(0), "a");
139 EXPECT_EQ(row(1), "hello, world");
140 EXPECT_EQ(row(2), "c");
141}
142
144{
145 std::istringstream ss("a,\"say \"\"hi\"\"\",c\n");
147
148 ASSERT_EQ(row.size(), 3u);
149 EXPECT_EQ(row(0), "a");
150 EXPECT_EQ(row(1), "say \"hi\"");
151 EXPECT_EQ(row(2), "c");
152}
153
155{
156 std::istringstream ss("a,\"line1\nline2\",c\n");
158
159 ASSERT_EQ(row.size(), 3u);
160 EXPECT_EQ(row(0), "a");
161 EXPECT_EQ(row(1), "line1\nline2");
162 EXPECT_EQ(row(2), "c");
163}
164
166{
167 std::istringstream ss("a,b,c\r\n");
169
170 ASSERT_EQ(row.size(), 3u);
171 EXPECT_EQ(row(0), "a");
172 EXPECT_EQ(row(1), "b");
173 EXPECT_EQ(row(2), "c");
174}
175
177{
178 std::istringstream ss("a,b,c");
180
181 ASSERT_EQ(row.size(), 3u);
182 EXPECT_EQ(row(0), "a");
183 EXPECT_EQ(row(1), "b");
184 EXPECT_EQ(row(2), "c");
185}
186
188{
189 std::istringstream ss("a;b;c\n");
191
192 ASSERT_EQ(row.size(), 3u);
193 EXPECT_EQ(row(0), "a");
194 EXPECT_EQ(row(1), "b");
195 EXPECT_EQ(row(2), "c");
196}
197
199{
200 std::istringstream ss("a\tb\tc\n");
202
203 ASSERT_EQ(row.size(), 3u);
204 EXPECT_EQ(row(0), "a");
205 EXPECT_EQ(row(1), "b");
206 EXPECT_EQ(row(2), "c");
207}
208
210{
211 std::istringstream ss("a,b,c\n1,2,3\nx,y,z\n");
212
216
217 ASSERT_EQ(row1.size(), 3u);
218 EXPECT_EQ(row1(0), "a");
219
220 ASSERT_EQ(row2.size(), 3u);
221 EXPECT_EQ(row2(0), "1");
222
223 ASSERT_EQ(row3.size(), 3u);
224 EXPECT_EQ(row3(0), "x");
225}
226
228{
229 std::istringstream ss("");
231
232 // Empty stream produces one empty field (consistent with CSV parsers)
233 // An empty string "" still has one field (empty)
234 EXPECT_LE(row.size(), 1u);
235}
236
238{
239 std::istringstream ss(" a , b , c \n");
241
242 ASSERT_EQ(row.size(), 3u);
243 EXPECT_EQ(row(0), " a ");
244 EXPECT_EQ(row(1), " b ");
245 EXPECT_EQ(row(2), " c ");
246}
247
248//============================================================================
249// csv_read_row Tests - String Input
250//============================================================================
251
252class CsvReadRowStringTest : public ::testing::Test {};
253
255{
256 std::string line = "a,b,c";
258
259 ASSERT_EQ(row.size(), 3u);
260 EXPECT_EQ(row(0), "a");
261 EXPECT_EQ(row(1), "b");
262 EXPECT_EQ(row(2), "c");
263}
264
266{
267 std::string line = "a,\"hello, world\",c";
269
270 ASSERT_EQ(row.size(), 3u);
271 EXPECT_EQ(row(1), "hello, world");
272}
273
275{
276 std::string line = "a,\"say \"\"hello\"\"\",c";
278
279 ASSERT_EQ(row.size(), 3u);
280 EXPECT_EQ(row(1), "say \"hello\"");
281}
282
283//============================================================================
284// csv_read_all Tests
285//============================================================================
286
287class CsvReadAllTest : public ::testing::Test {};
288
290{
291 std::istringstream ss("a,b,c\n1,2,3\nx,y,z\n");
293
294 ASSERT_EQ(data.size(), 3u);
295 EXPECT_EQ(data(0)(0), "a");
296 EXPECT_EQ(data(1)(0), "1");
297 EXPECT_EQ(data(2)(0), "x");
298}
299
301{
302 std::istringstream ss("a,b,c\n");
304
305 ASSERT_EQ(data.size(), 1u);
306 EXPECT_EQ(data(0).size(), 3u);
307}
308
310{
311 std::istringstream ss("a,b,c\n1,2,3");
313
314 ASSERT_EQ(data.size(), 2u);
315 EXPECT_EQ(data(1)(2), "3");
316}
317
319{
320 std::istringstream ss("");
322
323 // Empty stream may produce 0 or 1 empty rows depending on implementation
324 EXPECT_LE(data.size(), 1u);
325}
326
328{
329 std::istringstream ss("a,b,c\n1,2\nx,y,z,w\n");
331
332 ASSERT_EQ(data.size(), 3u);
333 EXPECT_EQ(data(0).size(), 3u);
334 EXPECT_EQ(data(1).size(), 2u);
335 EXPECT_EQ(data(2).size(), 4u);
336}
337
339{
340 std::istringstream ss("a;b;c\n1;2;3\n");
342
343 ASSERT_EQ(data.size(), 2u);
344 EXPECT_EQ(data(0)(1), "b");
345 EXPECT_EQ(data(1)(1), "2");
346}
347
348//============================================================================
349// csv_escape Tests
350//============================================================================
351
352class CsvEscapeTest : public ::testing::Test {};
353
355{
356 EXPECT_EQ(csv_escape("hello"), "hello");
357 EXPECT_EQ(csv_escape("world123"), "world123");
358 EXPECT_EQ(csv_escape(""), "");
359}
360
362{
363 EXPECT_EQ(csv_escape("hello, world"), "\"hello, world\"");
364}
365
367{
368 EXPECT_EQ(csv_escape("say \"hi\""), "\"say \"\"hi\"\"\"");
369}
370
372{
373 EXPECT_EQ(csv_escape("line1\nline2"), "\"line1\nline2\"");
374}
375
377{
378 EXPECT_EQ(csv_escape("line1\rline2"), "\"line1\rline2\"");
379}
380
382{
383 EXPECT_EQ(csv_escape("a,\"b\"\nc"), "\"a,\"\"b\"\"\nc\"");
384}
385
387{
388 EXPECT_EQ(csv_escape("a;b", ';'), "\"a;b\"");
389 EXPECT_EQ(csv_escape("a,b", ';'), "a,b"); // Comma not special with semicolon delimiter
390}
391
392//============================================================================
393// csv_write_row Tests
394//============================================================================
395
396class CsvWriteRowTest : public ::testing::Test {};
397
399{
401 row.append("a");
402 row.append("b");
403 row.append("c");
404
405 std::ostringstream out;
407
408 EXPECT_EQ(out.str(), "a,b,c\n");
409}
410
412{
414 row.append("name");
415 row.append("hello, world"); // Contains comma
416 row.append("value");
417
418 std::ostringstream out;
420
421 EXPECT_EQ(out.str(), "name,\"hello, world\",value\n");
422}
423
425{
427 row.append("say \"hi\"");
428
429 std::ostringstream out;
431
432 EXPECT_EQ(out.str(), "\"say \"\"hi\"\"\"\n");
433}
434
436{
438
439 std::ostringstream out;
441
442 EXPECT_EQ(out.str(), "\n");
443}
444
446{
448 row.append("single");
449
450 std::ostringstream out;
452
453 EXPECT_EQ(out.str(), "single\n");
454}
455
457{
459 row.append("a");
460 row.append("b");
461 row.append("c");
462
463 std::ostringstream out;
464 csv_write_row(out, row, ';');
465
466 EXPECT_EQ(out.str(), "a;b;c\n");
467}
468
470{
472 row.append("a");
473 row.append("b");
474
475 std::ostringstream out;
476 csv_write_row(out, row, ',', "\r\n");
477
478 EXPECT_EQ(out.str(), "a,b\r\n");
479}
480
481//============================================================================
482// csv_write_all Tests
483//============================================================================
484
485class CsvWriteAllTest : public ::testing::Test {};
486
488{
490
492 row1.append("a");
493 row1.append("b");
494 data.append(row1);
495
497 row2.append("1");
498 row2.append("2");
499 data.append(row2);
500
501 std::ostringstream out;
502 csv_write_all(out, data);
503
504 EXPECT_EQ(out.str(), "a,b\n1,2\n");
505}
506
508{
510
511 std::ostringstream out;
512 csv_write_all(out, data);
513
514 EXPECT_EQ(out.str(), "");
515}
516
517//============================================================================
518// csv_read_file / csv_write_file Tests
519//============================================================================
520
521class CsvFileTest : public ::testing::Test
522{
523protected:
524 void SetUp() override
525 {
526 // Unique per test case and process: rand() is unseeded, so it
527 // returns the same value in every freshly-launched process. Each
528 // TEST_F becomes its own ctest process (gtest_discover_tests), and
529 // CI runs ctest with --parallel, so two CsvFileTest cases racing on
530 // an identical rand()-based filename could stomp on each other's
531 // file mid-test.
532 const auto *test_info = ::testing::UnitTest::GetInstance()->current_test_info();
533 std::string base = std::string("aleph_csv_test_") + test_info->name() + "_" +
534 std::to_string(process_id());
535
536 for (auto &ch : base)
537 if (ch == '/' or ch == '\\' or ch == ' ')
538 ch = '_';
539
540 test_filename = (std::filesystem::temp_directory_path() / (base + ".csv")).string();
541 }
542
543 void TearDown() override
544 {
545 std::remove(test_filename.c_str());
546 }
547
548 std::string test_filename;
549};
550
552{
553 // Create test data
555
556 Array<std::string> header;
557 header.append("name");
558 header.append("value");
559 data.append(header);
560
562 row1.append("item1");
563 row1.append("100");
564 data.append(row1);
565
567 row2.append("item2");
568 row2.append("200");
569 data.append(row2);
570
571 // Write to file
572 ASSERT_NO_THROW(csv_write_file(test_filename, data));
573
574 // Read back
576 ASSERT_NO_THROW(read_data = csv_read_file(test_filename));
577
578 // Verify
579 ASSERT_EQ(read_data.size(), 3u);
580 EXPECT_EQ(read_data(0)(0), "name");
581 EXPECT_EQ(read_data(0)(1), "value");
582 EXPECT_EQ(read_data(1)(0), "item1");
583 EXPECT_EQ(read_data(1)(1), "100");
584 EXPECT_EQ(read_data(2)(0), "item2");
585 EXPECT_EQ(read_data(2)(1), "200");
586}
587
589{
590 EXPECT_THROW(csv_read_file("/nonexistent/path/file.csv"), std::runtime_error);
591}
592
594{
597 row.append("test");
598 data.append(row);
599
600 EXPECT_THROW(csv_write_file("/nonexistent/path/file.csv", data), std::runtime_error);
601}
602
604{
606
608 row.append("field with, comma");
609 row.append("field with \"quotes\"");
610 row.append("field with\nnewline");
611 data.append(row);
612
613 csv_write_file(test_filename, data);
615
616 ASSERT_EQ(read_data.size(), 1u);
617 ASSERT_EQ(read_data(0).size(), 3u);
618 EXPECT_EQ(read_data(0)(0), "field with, comma");
619 EXPECT_EQ(read_data(0)(1), "field with \"quotes\"");
620 EXPECT_EQ(read_data(0)(2), "field with\nnewline");
621}
622
623//============================================================================
624// csv_num_columns Tests
625//============================================================================
626
627class CsvNumColumnsTest : public ::testing::Test {};
628
630{
631 std::string line = "a,b,c,d,e";
633
635}
636
642
644{
645 std::string line = "single";
647
649}
650
651//============================================================================
652// csv_is_rectangular Tests
653//============================================================================
654
655class CsvIsRectangularTest : public ::testing::Test {};
656
662
664{
665 std::istringstream ss("a,b,c\n1,2,3\nx,y,z\n");
667
669}
670
672{
673 std::istringstream ss("a,b,c\n1,2\nx,y,z,w\n");
675
677}
678
680{
681 std::istringstream ss("a,b,c\n");
683
685}
686
687//============================================================================
688// csv_get_column Tests
689//============================================================================
690
691class CsvGetColumnTest : public ::testing::Test {};
692
694{
695 std::istringstream ss("name,age\nAlice,30\nBob,25\n");
697
698 Array<std::string> names = csv_get_column(data, 0);
699
700 ASSERT_EQ(names.size(), 3u);
701 EXPECT_EQ(names(0), "name");
702 EXPECT_EQ(names(1), "Alice");
703 EXPECT_EQ(names(2), "Bob");
704}
705
707{
708 std::istringstream ss("name,age\nAlice,30\nBob,25\n");
710
712
713 ASSERT_EQ(ages.size(), 3u);
714 EXPECT_EQ(ages(0), "age");
715 EXPECT_EQ(ages(1), "30");
716 EXPECT_EQ(ages(2), "25");
717}
718
720{
721 std::istringstream ss("a,b\n1,2\n");
723
724 EXPECT_THROW(csv_get_column(data, 5), std::out_of_range);
725}
726
728{
730 Array<std::string> column = csv_get_column(data, 0);
731
732 EXPECT_EQ(column.size(), 0u);
733}
734
735//============================================================================
736// csv_to_number Tests
737//============================================================================
738
739class CsvToNumberTest : public ::testing::Test {};
740
747
749{
750 EXPECT_EQ(csv_to_number<long>("1000000000"), 1000000000L);
751 EXPECT_EQ(csv_to_number<long>("-999999999"), -999999999L);
752}
753
760
766
768{
769 EXPECT_THROW((void)csv_to_number<int>("not_a_number"), std::invalid_argument);
770 EXPECT_THROW((void)csv_to_number<int>(""), std::invalid_argument);
771}
772
774{
775 EXPECT_THROW((void)csv_to_number<double>("abc"), std::invalid_argument);
776}
777
779{
780 // std::stoi handles leading whitespace
781 EXPECT_EQ(csv_to_number<int>(" 42"), 42);
782}
783
784//============================================================================
785// Round-trip Tests
786//============================================================================
787
788class CsvRoundTripTest : public ::testing::Test {};
789
791{
792 // Original data
793 std::istringstream input("name,value\ntest,100\n");
795
796 // Write to string
797 std::ostringstream output;
799
800 // Parse back
801 std::istringstream input2(output.str());
803
804 // Compare
805 ASSERT_EQ(parsed.size(), original.size());
806 for (size_t i = 0; i < original.size(); ++i)
807 {
808 ASSERT_EQ(parsed(i).size(), original(i).size());
809 for (size_t j = 0; j < original(i).size(); ++j)
810 EXPECT_EQ(parsed(i)(j), original(i)(j));
811 }
812}
813
815{
816 // Create data with special characters
818
820 row1.append("simple");
821 row1.append("with, comma");
822 row1.append("with \"quotes\"");
823 row1.append("with\nnewline");
824 original.append(row1);
825
827 row2.append("\"\"\""); // Just quotes
828 row2.append(",,,"); // Just commas
829 row2.append("\n\r\n"); // Newlines
830 row2.append(""); // Empty
831 original.append(row2);
832
833 // Round trip
834 std::ostringstream output;
836
837 std::istringstream input(output.str());
839
840 // Verify
841 ASSERT_EQ(parsed.size(), original.size());
842 for (size_t i = 0; i < original.size(); ++i)
843 {
844 ASSERT_EQ(parsed(i).size(), original(i).size())
845 << "Row " << i << " size mismatch";
846 for (size_t j = 0; j < original(i).size(); ++j)
847 EXPECT_EQ(parsed(i)(j), original(i)(j))
848 << "Mismatch at row " << i << ", col " << j;
849 }
850}
851
852//============================================================================
853// Edge Cases Tests
854//============================================================================
855
856class CsvEdgeCasesTest : public ::testing::Test {};
857
859{
860 std::istringstream ss("\n\n\n");
862
863 // Each newline produces an empty row with one empty field
864 EXPECT_GE(data.size(), 1u);
865}
866
868{
869 std::string line = ",,,";
871
872 // Four fields (three delimiters = four empty fields)
873 ASSERT_EQ(row.size(), 4u);
874 for (size_t i = 0; i < row.size(); ++i)
875 EXPECT_EQ(row(i), "");
876}
877
879{
880 std::string line = "a,\"\",b";
882
883 ASSERT_EQ(row.size(), 3u);
884 EXPECT_EQ(row(0), "a");
885 EXPECT_EQ(row(1), "");
886 EXPECT_EQ(row(2), "b");
887}
888
890{
891 std::string line = "\"\"\"\""; // Field containing single quote
893
894 ASSERT_EQ(row.size(), 1u);
895 EXPECT_EQ(row(0), "\"");
896}
897
899{
900 std::string long_string(10000, 'x');
901 std::string line = "a," + long_string + ",b";
903
904 ASSERT_EQ(row.size(), 3u);
906}
907
909{
910 std::string line;
911 for (int i = 0; i < 100; ++i)
912 {
913 if (i > 0) line += ",";
914 line += std::to_string(i);
915 }
916
918
919 ASSERT_EQ(row.size(), 100u);
920 EXPECT_EQ(row(0), "0");
921 EXPECT_EQ(row(99), "99");
922}
923
925{
926 std::string line = "日本語,中文,한국어";
928
929 ASSERT_EQ(row.size(), 3u);
930 EXPECT_EQ(row(0), "日本語");
931 EXPECT_EQ(row(1), "中文");
932 EXPECT_EQ(row(2), "한국어");
933}
934
935//============================================================================
936// Integration Tests
937//============================================================================
938
940{
941 // 1. Parse CSV header and data
942 std::istringstream input(
943 "id,name,price,quantity\n"
944 "1,Widget,9.99,100\n"
945 "2,\"Gadget, Pro\",19.99,50\n"
946 "3,\"Thing \"\"Deluxe\"\"\",29.99,25\n"
947 );
948
950
951 // 2. Verify structure
952 ASSERT_EQ(data.size(), 4u);
954
955 // 3. Access header
956 EXPECT_EQ(data(0)(0), "id");
957 EXPECT_EQ(data(0)(1), "name");
958 EXPECT_EQ(data(0)(2), "price");
959 EXPECT_EQ(data(0)(3), "quantity");
960
961 // 4. Parse numeric data
962 EXPECT_EQ(csv_to_number<int>(data(1)(0)), 1);
963 EXPECT_DOUBLE_EQ(csv_to_number<double>(data(1)(2)), 9.99);
964 EXPECT_EQ(csv_to_number<int>(data(1)(3)), 100);
965
966 // 5. Handle special characters
967 EXPECT_EQ(data(2)(1), "Gadget, Pro");
968 EXPECT_EQ(data(3)(1), "Thing \"Deluxe\"");
969
970 // 6. Extract a column
971 Array<std::string> names = csv_get_column(data, 1);
972 EXPECT_EQ(names(1), "Widget");
973 EXPECT_EQ(names(2), "Gadget, Pro");
974}
975
976//============================================================================
977// Type Traits Tests
978//============================================================================
979
981{
982 // Verify [[nodiscard]] attribute is respected
983 EXPECT_TRUE((std::is_same<decltype(csv_num_columns(std::declval<Array<std::string>&>())), size_t>::value));
984 EXPECT_TRUE((std::is_same<decltype(csv_is_rectangular(std::declval<Array<Array<std::string>>&>())), bool>::value));
985}
986
987//============================================================================
988// CsvRow Tests
989//============================================================================
990
991class CsvRowTest : public ::testing::Test {};
992
994{
995 Array<std::string> fields;
996 fields.append("a");
997 fields.append("b");
998 fields.append("c");
999
1000 CsvRow row(fields);
1001
1002 EXPECT_EQ(row[0], "a");
1003 EXPECT_EQ(row[1], "b");
1004 EXPECT_EQ(row[2], "c");
1005}
1006
1008{
1009 Array<std::string> header;
1010 header.append("name");
1011 header.append("age");
1012 header.append("city");
1013
1014 Array<std::string> fields;
1015 fields.append("Alice");
1016 fields.append("30");
1017 fields.append("NYC");
1018
1019 CsvRow row(fields, header);
1020
1021 EXPECT_EQ(row["name"], "Alice");
1022 EXPECT_EQ(row["age"], "30");
1023 EXPECT_EQ(row["city"], "NYC");
1024}
1025
1027{
1028 Array<std::string> fields;
1029 fields.append("value");
1030
1031 CsvRow row(fields);
1032
1033 EXPECT_THROW((void)row["column"], std::runtime_error);
1034}
1035
1037{
1038 Array<std::string> header;
1039 header.append("name");
1040
1041 Array<std::string> fields;
1042 fields.append("Alice");
1043
1044 CsvRow row(fields, header);
1045
1046 EXPECT_THROW((void)row["unknown"], std::out_of_range);
1047}
1048
1050{
1051 Array<std::string> header;
1052 header.append("name");
1053 header.append("age");
1054
1055 Array<std::string> fields;
1056 fields.append("Alice");
1057 fields.append("30");
1058
1059 CsvRow row(fields, header);
1060
1061 EXPECT_TRUE(row.has_column("name"));
1062 EXPECT_TRUE(row.has_column("age"));
1063 EXPECT_FALSE(row.has_column("unknown"));
1064}
1065
1067{
1068 Array<std::string> header;
1069 header.append("name");
1070 header.append("value");
1071
1072 Array<std::string> fields;
1073 fields.append("test");
1074 fields.append("42");
1075
1076 CsvRow row(fields, header);
1077
1078 EXPECT_EQ(row.get<int>(1), 42);
1079 EXPECT_EQ(row.get<int>("value"), 42);
1080}
1081
1083{
1084 Array<std::string> fields;
1085 CsvRow empty_row(fields);
1086 EXPECT_TRUE(empty_row.empty());
1087 EXPECT_EQ(empty_row.size(), 0u);
1088
1089 fields.append("a");
1090 CsvRow non_empty(fields);
1091 EXPECT_FALSE(non_empty.empty());
1092 EXPECT_EQ(non_empty.size(), 1u);
1093}
1094
1095//============================================================================
1096// CsvReader Tests
1097//============================================================================
1098
1099class CsvReaderTest : public ::testing::Test
1100{
1101protected:
1102 void SetUp() override
1103 {
1104 // Unique per test case and process: rand() is unseeded, so it
1105 // returns the same value in every freshly-launched process. Each
1106 // TEST_F becomes its own ctest process (gtest_discover_tests), and
1107 // CI runs ctest with --parallel, so two CsvReaderTest cases racing
1108 // on an identical rand()-based filename could have one process's
1109 // TearDown() remove the file while another's test body is still
1110 // reading it -- exactly the intermittent "Cannot open file" seen
1111 // on the macOS runners.
1112 const auto *test_info = ::testing::UnitTest::GetInstance()->current_test_info();
1113 std::string base = std::string("aleph_csv_reader_test_") + test_info->name() + "_" +
1114 std::to_string(process_id());
1115
1116 for (auto &ch : base)
1117 if (ch == '/' or ch == '\\' or ch == ' ')
1118 ch = '_';
1119
1120 test_filename = (std::filesystem::temp_directory_path() / (base + ".csv")).string();
1121
1122 // Create test file
1123 std::ofstream file(test_filename);
1124 file << "name,age,city\n";
1125 file << "Alice,30,NYC\n";
1126 file << "Bob,25,LA\n";
1127 file << "Charlie,35,Chicago\n";
1128 file.close();
1129 }
1130
1131 void TearDown() override
1132 {
1133 std::remove(test_filename.c_str());
1134 }
1135
1136 std::string test_filename;
1137};
1138
1140{
1141 CsvReader reader(test_filename);
1142 reader.read_header();
1143
1144 EXPECT_TRUE(reader.has_header());
1145 EXPECT_EQ(reader.header().size(), 3u);
1146 EXPECT_EQ(reader.header()(0), "name");
1147}
1148
1150{
1151 CsvReader reader(test_filename);
1152 reader.read_header();
1153
1154 EXPECT_TRUE(reader.has_next());
1155
1156 CsvRow row1 = reader.next_row();
1157 EXPECT_EQ(row1["name"], "Alice");
1158 EXPECT_EQ(row1["age"], "30");
1159
1160 CsvRow row2 = reader.next_row();
1161 EXPECT_EQ(row2["name"], "Bob");
1162
1163 CsvRow row3 = reader.next_row();
1164 EXPECT_EQ(row3["name"], "Charlie");
1165
1166 EXPECT_FALSE(reader.has_next());
1167}
1168
1170{
1171 CsvReader reader(test_filename);
1172 reader.read_header();
1173 reader.skip(1); // Skip Alice
1174
1175 CsvRow row = reader.next_row();
1176 EXPECT_EQ(row["name"], "Bob");
1177}
1178
1180{
1181 CsvReader reader(test_filename);
1182 reader.read_header();
1183
1184 EXPECT_EQ(reader.rows_read(), 0u);
1185 reader.next();
1186 EXPECT_EQ(reader.rows_read(), 1u);
1187 reader.next();
1188 EXPECT_EQ(reader.rows_read(), 2u);
1189}
1190
1192{
1193 CsvReader reader(test_filename);
1194 reader.read_header();
1195
1196 size_t count = 0;
1197 for (const auto & row : reader)
1198 {
1199 (void)row;
1200 ++count;
1201 }
1202
1203 EXPECT_EQ(count, 3u);
1204}
1205
1207{
1208 std::istringstream ss("a,b\n1,2\n");
1210
1212 EXPECT_EQ(row1(0), "a");
1213
1215 EXPECT_EQ(row2(0), "1");
1216}
1217
1218//============================================================================
1219// Filter and Selection Tests
1220//============================================================================
1221
1222class CsvFilterTest : public ::testing::Test
1223{
1224protected:
1225 void SetUp() override
1226 {
1227 std::istringstream ss("name,status\nAlice,active\nBob,inactive\nCharlie,active\n");
1228 data = csv_read_all(ss);
1229 }
1230
1232};
1233
1235{
1236 auto result = csv_filter(data, [](const Array<std::string> & row) {
1237 return row.size() > 1 and row(1) == "active";
1238 });
1239
1240 EXPECT_EQ(result.size(), 2u); // Alice and Charlie
1241}
1242
1244{
1245 auto result = csv_filter_by_value(data, 1, "active");
1246
1247 EXPECT_EQ(result.size(), 2u);
1248}
1249
1251{
1253 cols.append(0); // name only
1254
1255 auto result = csv_select_columns(data, cols);
1256
1257 EXPECT_EQ(result.size(), data.size());
1258 EXPECT_EQ(result(0).size(), 1u);
1259 EXPECT_EQ(result(0)(0), "name");
1260}
1261
1263{
1264 auto result = csv_skip_rows(data, 1); // Skip header
1265
1266 EXPECT_EQ(result.size(), 3u);
1267 EXPECT_EQ(result(0)(0), "Alice");
1268}
1269
1271{
1272 auto result = csv_take_rows(data, 2); // Header + Alice
1273
1274 EXPECT_EQ(result.size(), 2u);
1275 EXPECT_EQ(result(1)(0), "Alice");
1276}
1277
1278//============================================================================
1279// Statistics Tests
1280//============================================================================
1281
1282class CsvStatisticsTest : public ::testing::Test
1283{
1284protected:
1285 void SetUp() override
1286 {
1287 std::istringstream ss("a,b,c\n1,,3\n4,5,\n7,8,9\n");
1288 data = csv_read_all(ss);
1289 }
1290
1292};
1293
1298
1300{
1301 EXPECT_EQ(csv_count_empty(data), 2u); // Two empty fields
1302}
1303
1305{
1306 auto count = csv_count_if(data, [](const Array<std::string> & row) {
1307 return row.size() >= 3 and row(2) == "";
1308 });
1309
1310 EXPECT_EQ(count, 1u); // Row "4,5,"
1311}
1312
1314{
1315 auto idx = csv_find_row(data, [](const Array<std::string> & row) {
1316 return row.size() > 0 and row(0) == "4";
1317 });
1318
1319 EXPECT_EQ(idx, 2u);
1320}
1321
1323{
1324 auto idx = csv_find_by_value(data, 0, "7");
1325 EXPECT_EQ(idx, 3u);
1326}
1327
1329{
1330 auto idx = csv_find_by_value(data, 0, "xyz");
1331 EXPECT_EQ(idx, data.size()); // Not found
1332}
1333
1335{
1336 std::istringstream ss("cat\ndog\ncat\nbird\ndog\n");
1337 auto animals = csv_read_all(ss);
1338
1339 auto unique = csv_distinct(animals, 0);
1340 EXPECT_EQ(unique.size(), 3u); // cat, dog, bird
1341}
1342
1343//============================================================================
1344// Transformation Tests
1345//============================================================================
1346
1347class CsvTransformationTest : public ::testing::Test
1348{
1349protected:
1350 void SetUp() override
1351 {
1352 std::istringstream ss("a,b,c\n1,2,3\n4,5,6\n");
1353 data = csv_read_all(ss);
1354 }
1355
1357};
1358
1360{
1361 auto result = csv_transpose(data);
1362
1363 EXPECT_EQ(result.size(), 3u); // 3 columns become 3 rows
1364 EXPECT_EQ(result(0).size(), 3u); // 3 rows become 3 columns
1365 EXPECT_EQ(result(0)(0), "a");
1366 EXPECT_EQ(result(0)(1), "1");
1367 EXPECT_EQ(result(0)(2), "4");
1368}
1369
1371{
1372 std::istringstream ss("charlie,3\nalice,1\nbob,2\n");
1373 auto unsorted = csv_read_all(ss);
1374
1375 auto sorted = csv_sort_by_column(unsorted, 0); // Sort by name
1376
1377 EXPECT_EQ(sorted(0)(0), "alice");
1378 EXPECT_EQ(sorted(1)(0), "bob");
1379 EXPECT_EQ(sorted(2)(0), "charlie");
1380}
1381
1383{
1384 std::istringstream ss("charlie,30\nalice,10\nbob,20\n");
1385 auto unsorted = csv_read_all(ss);
1386
1387 auto sorted = csv_sort_by_column_numeric<int>(unsorted, 1); // Sort by value
1388
1389 EXPECT_EQ(sorted(0)(0), "alice"); // 10
1390 EXPECT_EQ(sorted(1)(0), "bob"); // 20
1391 EXPECT_EQ(sorted(2)(0), "charlie"); // 30
1392}
1393
1395{
1396 std::istringstream ss("a,b\n1,2\n3,4\n1,2\n5,6\n1,2\n");
1397 auto with_dupes = csv_read_all(ss);
1398
1400
1401 EXPECT_EQ(unique.size(), 4u); // header + 3 unique data rows
1402}
1403
1405{
1406 auto upper = csv_transform(data, [](const std::string & s) {
1407 std::string result = s;
1408 for (char & c : result)
1409 c = std::toupper(static_cast<unsigned char>(c));
1410 return result;
1411 });
1412
1413 EXPECT_EQ(upper(0)(0), "A");
1414 EXPECT_EQ(upper(0)(1), "B");
1415}
1416
1418{
1419 auto with_sum = csv_add_column(data, [](const Array<std::string> & row) {
1420 if (row.size() < 3) return std::string("N/A");
1421 try {
1422 int sum = std::stoi(row(0)) + std::stoi(row(1)) + std::stoi(row(2));
1423 return std::to_string(sum);
1424 } catch (...) {
1425 return std::string("N/A");
1426 }
1427 });
1428
1429 EXPECT_EQ(with_sum(0).size(), 4u); // Original 3 + new column
1430 EXPECT_EQ(with_sum(1)(3), "6"); // 1+2+3
1431 EXPECT_EQ(with_sum(2)(3), "15"); // 4+5+6
1432}
1433
1435{
1436 auto renamed = csv_rename_column(data, "a", "first_col");
1437
1438 EXPECT_EQ(renamed(0)(0), "first_col");
1439 EXPECT_EQ(renamed(0)(1), "b"); // Unchanged
1440}
1441
1442//============================================================================
1443// Utility Tests
1444//============================================================================
1445
1446class CsvUtilityTest : public ::testing::Test {};
1447
1449{
1450 // Create stream with UTF-8 BOM (3 bytes: EF BB BF)
1451 std::string bom_str;
1452 bom_str += '\xEF';
1453 bom_str += '\xBB';
1454 bom_str += '\xBF';
1455 bom_str += "a,b,c\n";
1456 std::istringstream ss(bom_str);
1457
1459
1460 auto row = csv_read_row(ss);
1461 EXPECT_EQ(row(0), "a");
1462}
1463
1465{
1466 std::istringstream ss("a,b,c\n");
1467
1469
1470 auto row = csv_read_row(ss);
1471 EXPECT_EQ(row(0), "a"); // Still reads correctly
1472}
1473
1475{
1476 std::istringstream ss(" a , b , c \n");
1477 auto data = csv_read_all(ss);
1478
1479 auto trimmed = csv_trim_fields(data);
1480
1481 EXPECT_EQ(trimmed(0)(0), "a");
1482 EXPECT_EQ(trimmed(0)(1), "b");
1483 EXPECT_EQ(trimmed(0)(2), "c");
1484}
1485
1487{
1488 std::istringstream ss("a,,c\n");
1489 auto data = csv_read_all(ss);
1490
1491 auto filled = csv_fill_empty(data, "N/A");
1492
1493 EXPECT_EQ(filled(0)(0), "a");
1494 EXPECT_EQ(filled(0)(1), "N/A");
1495 EXPECT_EQ(filled(0)(2), "c");
1496}
1497
1498//============================================================================
1499// Join Tests
1500//============================================================================
1501
1502class CsvJoinTest : public ::testing::Test {};
1503
1505{
1506 std::istringstream ss1("a,b\n1,2\n");
1507 std::istringstream ss2("c,d\n3,4\n");
1508
1509 auto left = csv_read_all(ss1);
1510 auto right = csv_read_all(ss2);
1511
1512 auto joined = csv_join_horizontal(left, right);
1513
1514 EXPECT_EQ(joined.size(), 2u);
1515 EXPECT_EQ(joined(0).size(), 4u);
1516 EXPECT_EQ(joined(0)(0), "a");
1517 EXPECT_EQ(joined(0)(2), "c");
1518}
1519
1521{
1522 std::istringstream ss1("a,b\n1,2\n");
1523 std::istringstream ss2("3,4\n5,6\n");
1524
1525 auto top = csv_read_all(ss1);
1526 auto bottom = csv_read_all(ss2);
1527
1528 auto joined = csv_join_vertical(top, bottom);
1529
1530 EXPECT_EQ(joined.size(), 4u);
1531 EXPECT_EQ(joined(2)(0), "3");
1532}
1533
1535{
1536 std::istringstream ss1("id,name\n1,Alice\n2,Bob\n3,Charlie\n");
1537 std::istringstream ss2("id,dept\n1,Sales\n3,Engineering\n4,HR\n");
1538
1539 auto left = csv_read_all(ss1);
1540 auto right = csv_read_all(ss2);
1541
1542 // Skip headers for join
1543 auto left_data = csv_skip_rows(left, 1);
1544 auto right_data = csv_skip_rows(right, 1);
1545
1547
1548 EXPECT_EQ(joined.size(), 2u); // Only 1 and 3 match
1549 EXPECT_EQ(joined(0)(1), "Alice");
1550 EXPECT_EQ(joined(0)(2), "Sales");
1551}
1552
1554{
1555 std::istringstream ss("dept,name\nSales,Alice\nEng,Bob\nSales,Charlie\nEng,Dave\n");
1556 auto data = csv_read_all(ss);
1557 auto data_rows = csv_skip_rows(data, 1);
1558
1559 auto groups = csv_group_by(data_rows, 0);
1560
1561 EXPECT_EQ(groups.size(), 2u); // Sales and Eng
1562
1563 // Each group should have 2 members
1564 EXPECT_EQ(groups(0).size(), 2u);
1565 EXPECT_EQ(groups(1).size(), 2u);
1566}
1567
1568//============================================================================
1569// Main
1570//============================================================================
1571
1572int main(int argc, char **argv)
1573{
1574 ::testing::InitGoogleTest(&argc, argv);
1575 return RUN_ALL_TESTS();
1576}
int main()
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
size_t row
Definition ca-c-api.h:115
size_t cols
Definition ca-c-api.h:105
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
Lazy CSV reader for large files.
Definition parse-csv.H:711
A CSV row with header-based field access.
Definition parse-csv.H:601
void SetUp() override
void TearDown() override
std::string test_filename
Array< Array< std::string > > data
void SetUp() override
void SetUp() override
void TearDown() override
std::string test_filename
void SetUp() override
Array< Array< std::string > > data
Array< Array< std::string > > data
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
long csv_to_number< long >(const std::string &field)
Definition parse-csv.H:555
size_t csv_find_row(const Array< Array< std::string > > &rows, Pred predicate)
Find first row matching a predicate.
Definition parse-csv.H:1010
Array< Array< std::string > > csv_trim_fields(const Array< Array< std::string > > &rows)
Trim whitespace from all fields.
Definition parse-csv.H:1273
std::string csv_escape(const std::string &field, char delimiter=',')
Escape a string for CSV output.
Definition parse-csv.H:320
Array< Array< std::string > > csv_read_file(const std::string &filename, char delimiter=',')
Read all rows from a CSV file.
Definition parse-csv.H:290
Array< Array< std::string > > csv_transform(const Array< Array< std::string > > &rows, Func func)
Apply a transformation to each field.
Definition parse-csv.H:1219
Itor unique(Itor __first, Itor __last, BinaryPredicate __binary_pred=BinaryPredicate())
Remove consecutive duplicates in place.
Definition ahAlgo.H:1058
Array< Array< std::string > > csv_filter_by_value(const Array< Array< std::string > > &rows, size_t col_index, const std::string &value)
Filter CSV rows by column value.
Definition parse-csv.H:880
size_t size(Node *root) noexcept
bool csv_skip_bom(std::istream &in)
Skip UTF-8 BOM if present.
Definition parse-csv.H:1250
Array< Array< std::string > > csv_transpose(const Array< Array< std::string > > &rows)
Transpose CSV data (swap rows and columns).
Definition parse-csv.H:1067
Array< Array< std::string > > csv_rename_column(const Array< Array< std::string > > &rows, const std::string &old_name, const std::string &new_name)
Rename a column (in the header row).
Definition parse-csv.H:1490
Array< Array< std::string > > csv_take_rows(const Array< Array< std::string > > &rows, size_t n)
Take only the first N rows of CSV data.
Definition parse-csv.H:955
and
Check uniqueness with explicit hash + equality functors.
Array< std::string > csv_get_column(const Array< Array< std::string > > &rows, size_t col_index)
Get a column from CSV data.
Definition parse-csv.H:497
Array< Array< std::string > > csv_join_horizontal(const Array< Array< std::string > > &left, const Array< Array< std::string > > &right)
Join two CSV datasets horizontally (add columns).
Definition parse-csv.H:1319
Array< Array< std::string > > csv_unique(const Array< Array< std::string > > &rows)
Remove duplicate rows.
Definition parse-csv.H:1183
Array< Array< Array< std::string > > > csv_group_by(const Array< Array< std::string > > &rows, size_t col_index)
Group rows by a column value.
Definition parse-csv.H:1424
float csv_to_number< float >(const std::string &field)
Definition parse-csv.H:569
void csv_write_row(std::ostream &out, const Array< std::string > &row, char delimiter=',', const std::string &line_ending="\n")
Write a CSV row to an output stream.
Definition parse-csv.H:376
size_t csv_num_columns(const Array< std::string > &row)
Get the number of columns in a CSV row.
Definition parse-csv.H:444
size_t csv_count_empty(const Array< Array< std::string > > &rows)
Count empty fields in CSV data.
Definition parse-csv.H:981
Array< std::string > csv_read_row(std::istream &in, char delimiter=',')
Read a single CSV row from an input stream.
Definition parse-csv.H:158
size_t csv_find_by_value(const Array< Array< std::string > > &rows, size_t col_index, const std::string &value)
Find row where column equals value.
Definition parse-csv.H:1023
bool csv_is_rectangular(const Array< Array< std::string > > &rows)
Check if all rows have the same number of columns.
Definition parse-csv.H:466
Array< std::string > csv_distinct(const Array< Array< std::string > > &rows, size_t col_index)
Get distinct values in a column.
Definition parse-csv.H:1036
Array< Array< std::string > > csv_filter(const Array< Array< std::string > > &rows, Pred predicate)
Filter CSV rows by a predicate.
Definition parse-csv.H:859
Array< Array< std::string > > csv_read_all(std::istream &in, char delimiter=',')
Read all rows from a CSV input stream.
Definition parse-csv.H:253
int csv_to_number< int >(const std::string &field)
Definition parse-csv.H:548
double csv_to_number< double >(const std::string &field)
Definition parse-csv.H:562
Array< Array< std::string > > csv_inner_join(const Array< Array< std::string > > &left, size_t left_key_col, const Array< Array< std::string > > &right, size_t right_key_col)
Inner join two CSV datasets by a key column.
Definition parse-csv.H:1379
void csv_write_file(const std::string &filename, const Array< Array< std::string > > &rows, char delimiter=',', const std::string &line_ending="\n")
Write CSV data to a file.
Definition parse-csv.H:424
Array< Array< std::string > > csv_skip_rows(const Array< Array< std::string > > &rows, size_t n)
Skip the first N rows of CSV data.
Definition parse-csv.H:937
Array< Array< std::string > > csv_select_columns(const Array< Array< std::string > > &rows, const Array< size_t > &col_indices)
Select specific columns from CSV data.
Definition parse-csv.H:906
Array< Array< std::string > > csv_fill_empty(const Array< Array< std::string > > &rows, const std::string &default_value)
Replace empty fields with a default value.
Definition parse-csv.H:1299
size_t csv_count_if(const Array< Array< std::string > > &rows, Pred predicate)
Count rows matching a predicate.
Definition parse-csv.H:995
Array< Array< std::string > > csv_join_vertical(const Array< Array< std::string > > &top, const Array< Array< std::string > > &bottom)
Join two CSV datasets vertically (add rows).
Definition parse-csv.H:1354
Array< Array< std::string > > csv_add_column(const Array< Array< std::string > > &rows, Func func)
Add a new column with computed values.
Definition parse-csv.H:1463
Array< Array< std::string > > csv_sort_by_column(const Array< Array< std::string > > &rows, size_t col_index, bool ascending=true)
Sort CSV data by a column.
Definition parse-csv.H:1100
void csv_write_all(std::ostream &out, const Array< Array< std::string > > &rows, char delimiter=',', const std::string &line_ending="\n")
Write multiple CSV rows to an output stream.
Definition parse-csv.H:402
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
size_t csv_count_rows(const Array< Array< std::string > > &rows)
Count total number of rows.
Definition parse-csv.H:973
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
Comprehensive CSV (Comma-Separated Values) parsing and manipulation utilities.
TEST_F(CsvReadRowStreamTest, SimpleRow)
ofstream output
Definition writeHeap.C:215
ofstream file
Definition writeRb.C:51