Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
load_digraph_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
38#include <gtest/gtest.h>
39#include <sstream>
40#include <string>
41
42#include "load_digraph.H"
43
44using namespace Aleph;
45
46namespace
47{
48std::string make_nodes_csv()
49{
50 return
51 "id|plazo|class|f3|nes|power|label\n" // header (ignored)
52 "A|cp|it|x|12|5|Alpha\n"
53 "B|mp|ef|x|34|7|Beta\n"
54 "C|lp|pp|x|56|9|Gamma\n";
55}
56
57std::string make_arcs_csv()
58{
59 return
60 "A B\n"
61 "B C\n"
62 "C A\n"; // completes a cycle
63}
64} // namespace
65
67{
68 bool wp = with_power;
69 bool wn = with_nes;
70 bool on = only_num;
71 bool wc = with_class;
72 size_t fs = font_size;
73
75 {
76 with_power = false;
77 with_nes = false;
78 only_num = false;
79 with_class = false;
80 font_size = 6;
81 }
82
84 {
85 with_power = wp;
86 with_nes = wn;
87 only_num = on;
88 with_class = wc;
89 font_size = fs;
90 }
91};
92
94{
96 std::stringstream nodes(make_nodes_csv());
97 nodes << "Z|cp|it\n"; // malformed, should be skipped
98 std::stringstream arcs(make_arcs_csv());
99
100 Digrafo g;
102
103 EXPECT_EQ(g.get_num_nodes(), 3U);
104 EXPECT_EQ(g.get_num_arcs(), 3U);
105
106 auto *a = search_node(g, "A");
107 ASSERT_NE(a, nullptr);
108 EXPECT_EQ(a->get_info().first, "A");
109 EXPECT_EQ(a->get_info().second[6], "Alpha");
110}
111
113{
114 FlagReset _;
115 std::stringstream nodes; // empty -> no nodes added
116 std::stringstream arcs;
117 arcs << "X Y\n";
118
119 Digrafo g;
121
122 EXPECT_EQ(g.get_num_nodes(), 2U);
123 EXPECT_EQ(g.get_num_arcs(), 1U);
124
125 // Nodes created from arcs have empty fields
126 auto *x = search_node(g, "X");
127 ASSERT_NE(x, nullptr);
128 EXPECT_TRUE(x->get_info().second.is_empty());
129}
130
132{
133 FlagReset _;
134 std::stringstream nodes;
135 std::stringstream arcs;
136
137 Digrafo g;
139
140 EXPECT_EQ(g.get_num_nodes(), 0U);
141 EXPECT_EQ(g.get_num_arcs(), 0U);
142}
143
145{
146 FlagReset _;
147 std::stringstream nodes;
148 nodes << "id|plazo|class|f3|nes|power|label\n"; // header
149 nodes << "D|||\n"; // too few fields
150 nodes << "E|cp|it|x|44|11|Echo\n"; // valid
151
152 std::stringstream arcs;
153 arcs << "D E\n"; // arc creates D with empty fields
154
155 Digrafo g;
157
158 EXPECT_EQ(g.get_num_nodes(), 2U);
159 EXPECT_EQ(g.get_num_arcs(), 1U);
160
161 auto *d = search_node(g, "D");
162 ASSERT_NE(d, nullptr);
163 EXPECT_TRUE(d->get_info().second.is_empty()); // created from arc, ignored bad row
164
165 auto *e = search_node(g, "E");
166 ASSERT_NE(e, nullptr);
167 EXPECT_EQ(e->get_info().second.size(), 7U); // valid row stored
168}
169
171{
172 FlagReset _;
173 std::stringstream nodes(make_nodes_csv());
174 std::stringstream arcs(make_arcs_csv()); // cycle
175 Digrafo g;
177
178 // Configure flags
179 with_power = true;
180 with_nes = true;
181 only_num = false;
182 with_class = true;
183 font_size = 8;
184
185 std::stringstream out;
187 const auto dot = out.str();
188
189 EXPECT_NE(dot.find("WARNING: Cycle detected"), std::string::npos);
190 EXPECT_NE(dot.find("A [color=Green"), std::string::npos);
191 EXPECT_NE(dot.find("\\nP=5"), std::string::npos);
192 EXPECT_NE(dot.find("\\n12%"), std::string::npos);
193 EXPECT_NE(dot.find("shape = box"), std::string::npos);
194}
195
197{
198 FlagReset _;
199 std::stringstream nodes(make_nodes_csv());
200 std::stringstream arcs;
201 arcs << "A B\n"
202 << "A C\n"; // DAG
203 Digrafo g;
205
206 with_power = false;
207 with_nes = false;
208 only_num = true;
209 with_class = false;
210 font_size = 6;
211
212 std::stringstream out;
214 const auto dot = out.str();
215
216 EXPECT_EQ(dot.find("WARNING: Cycle detected"), std::string::npos);
217 EXPECT_NE(dot.find("{ rank = same;"), std::string::npos);
218 EXPECT_NE(dot.find("A -> B"), std::string::npos);
219 EXPECT_NE(dot.find("A -> C"), std::string::npos);
220}
221
223{
224 FlagReset _;
225 std::stringstream nodes(make_nodes_csv());
226 std::stringstream arcs;
227 arcs << "A B\n"; // simple DAG
228 Digrafo g;
230
231 only_num = true;
232 with_power = false;
233 with_nes = false;
234 with_class = false;
235 font_size = 10;
236
237 std::stringstream out;
239 const auto dot = out.str();
240
241 // Should not include labels or power/nes when only_num is true
242 EXPECT_EQ(dot.find("Alpha"), std::string::npos);
243 EXPECT_EQ(dot.find("P="), std::string::npos);
244 EXPECT_EQ(dot.find("%"), std::string::npos);
245 EXPECT_NE(dot.find("fontsize = 10"), std::string::npos);
246}
247
248// ==================== Additional Tests ====================
249
251{
253 split("a|b|c", "|", words);
254 ASSERT_EQ(words.size(), 3U);
255 EXPECT_EQ(words[0], "a");
256 EXPECT_EQ(words[1], "b");
257 EXPECT_EQ(words[2], "c");
258}
259
261{
263 split("a,b c d", " ,", words);
264 ASSERT_EQ(words.size(), 4U);
265 EXPECT_EQ(words[0], "a");
266 EXPECT_EQ(words[1], "b");
267 EXPECT_EQ(words[2], "c");
268 EXPECT_EQ(words[3], "d");
269}
270
272{
274 split("", "|", words);
275 EXPECT_EQ(words.size(), 0U);
276}
277
279{
281 split("|||", "|", words);
282 EXPECT_EQ(words.size(), 0U);
283}
284
286{
288 split("hello", "|", words);
289 ASSERT_EQ(words.size(), 1U);
290 EXPECT_EQ(words[0], "hello");
291}
292
294{
296 split("|a|b|", "|", words);
297 ASSERT_EQ(words.size(), 2U);
298 EXPECT_EQ(words[0], "a");
299 EXPECT_EQ(words[1], "b");
300}
301
303{
306 fields1.append("x");
307 fields2.append("y");
308
309 Info_Nodo n1("A", fields1);
310 Info_Nodo n2("A", fields2);
311 Info_Nodo n3("B", fields1);
312
313 EXPECT_TRUE(eq(n1, n2)); // Same ID, different fields
314 EXPECT_FALSE(eq(n1, n3)); // Different IDs
315}
316
318{
319 FlagReset _;
320 std::stringstream nodes;
321 std::stringstream arcs;
322 arcs << "X,Y\n" // comma-separated
323 << "Y Z\n"; // space-separated
324
325 Digrafo g;
327
328 EXPECT_EQ(g.get_num_nodes(), 3U);
329 EXPECT_EQ(g.get_num_arcs(), 2U);
330}
331
333{
334 FlagReset _;
335 std::stringstream nodes(make_nodes_csv());
336 std::stringstream arcs;
337 arcs << "A A\n"; // Self-loop
338
339 Digrafo g;
341
342 EXPECT_EQ(g.get_num_nodes(), 3U);
343 EXPECT_EQ(g.get_num_arcs(), 1U);
344
345 // Verify self-loop
346 bool found_self_loop = false;
347 for (Arc_Iterator<Digrafo> it(g); it.has_curr(); it.next_ne())
348 {
349 auto *a = it.get_curr();
350 if (g.get_src_node(a) == g.get_tgt_node(a))
351 found_self_loop = true;
352 }
354}
355
357{
358 FlagReset _;
359 std::stringstream nodes;
360 std::stringstream arcs;
361 arcs << "A B\n"
362 << "A B\n"; // Duplicate arc
363
364 Digrafo g;
366
367 EXPECT_EQ(g.get_num_nodes(), 2U);
368 EXPECT_EQ(g.get_num_arcs(), 2U); // Both arcs are inserted
369}
370
372{
373 FlagReset _;
374 std::stringstream nodes;
375 nodes << "id|plazo|class|f3|nes|power|label\n"
376 << "A|cp|it|x|10|1|Node1\n" // Green, box
377 << "B|mp|ef|x|20|2|Node2\n" // Yellow, ellipse
378 << "C|lp|pp|x|30|3|Node3\n" // Red, hexagon
379 << "D|unknown|unknown|x|40|4|Node4\n"; // Unknown color/shape
380
381 std::stringstream arcs;
382 arcs << "A B\n"
383 << "B C\n"
384 << "C D\n";
385
386 Digrafo g;
388
389 with_power = true;
390 with_nes = true;
391 with_class = true;
392 only_num = false;
393 font_size = 12;
394
395 std::stringstream out;
397 const auto dot = out.str();
398
399 // Check all colors
400 EXPECT_NE(dot.find("A [color=Green"), std::string::npos);
401 EXPECT_NE(dot.find("B [color=Yellow"), std::string::npos);
402 EXPECT_NE(dot.find("C [color=Red"), std::string::npos);
403
404 // Check all shapes
405 EXPECT_NE(dot.find("shape = box"), std::string::npos);
406 EXPECT_NE(dot.find("shape = ellipse"), std::string::npos);
407 EXPECT_NE(dot.find("shape = hexagon"), std::string::npos);
408
409 // Check power and NES
410 EXPECT_NE(dot.find("P=1"), std::string::npos);
411 EXPECT_NE(dot.find("P=2"), std::string::npos);
412 EXPECT_NE(dot.find("10%"), std::string::npos);
413 EXPECT_NE(dot.find("20%"), std::string::npos);
414}
415
417{
418 FlagReset _;
419 std::stringstream nodes;
420 nodes << "id|plazo|class|f3|nes|power|label\n"
421 << "X|cp|it|x|50|5|Single\n";
422 std::stringstream arcs;
423
424 Digrafo g;
426
427 only_num = false;
428
429 std::stringstream out;
431 const auto dot = out.str();
432
433 EXPECT_NE(dot.find("X [color=Green"), std::string::npos);
434 EXPECT_NE(dot.find("Single"), std::string::npos);
435 EXPECT_EQ(dot.find("->"), std::string::npos); // No arcs
436}
437
439{
440 FlagReset _;
441 std::stringstream nodes;
442 nodes << "id|plazo|class|f3|nes|power|label\n"
443 << "A|cp|it|x|10|1|A\n"
444 << "B|mp|ef|x|20|2|B\n"
445 << "C|lp|pp|x|30|3|C\n"
446 << "D|cp|it|x|40|4|D\n";
447 std::stringstream arcs;
448 arcs << "A B\n"
449 << "C D\n"; // Two disconnected components
450
451 Digrafo g;
453
454 only_num = false;
455
456 std::stringstream out;
458 const auto dot = out.str();
459
460 EXPECT_EQ(dot.find("WARNING: Cycle"), std::string::npos);
461 EXPECT_NE(dot.find("A -> B"), std::string::npos);
462 EXPECT_NE(dot.find("C -> D"), std::string::npos);
463}
464
466{
467 Digrafo g;
468 g.insert_node(Info_Nodo("existing", DynDlist<std::string>()));
469
470 auto *found = search_node(g, "existing");
471 ASSERT_NE(found, nullptr);
472 EXPECT_EQ(found->get_info().first, "existing");
473
474 // Verify no new nodes were created
475 EXPECT_EQ(g.get_num_nodes(), 1U);
476}
477
479{
480 Digrafo g;
481
482 auto *created = search_node(g, "new_node");
483 ASSERT_NE(created, nullptr);
484 EXPECT_EQ(created->get_info().first, "new_node");
485 EXPECT_TRUE(created->get_info().second.is_empty());
486 EXPECT_EQ(g.get_num_nodes(), 1U);
487}
488
493
495{
496 FlagReset _;
497 std::stringstream nodes;
498 nodes << "id|plazo|class|f3|nes|power|label\n";
499
500 constexpr size_t NUM_NODES = 100;
501 for (size_t i = 0; i < NUM_NODES; ++i)
502 {
503 nodes << "N" << i << "|cp|it|x|10|5|Node" << i << "\n";
504 }
505
506 std::stringstream arcs;
507 for (size_t i = 0; i < NUM_NODES - 1; ++i)
508 {
509 arcs << "N" << i << " N" << (i + 1) << "\n";
510 }
511
512 Digrafo g;
514
515 EXPECT_EQ(g.get_num_nodes(), NUM_NODES);
516 EXPECT_EQ(g.get_num_arcs(), NUM_NODES - 1);
517
518 // Verify it's a chain (DAG)
519 std::stringstream out;
521 const auto dot = out.str();
522 EXPECT_EQ(dot.find("WARNING: Cycle"), std::string::npos);
523}
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3848
Dynamic doubly linked list with O(1) size and bidirectional access.
const size_t & size() const noexcept
Return the number of elements (constant time)
T & append(const T &item)
Append a new item by copy.
Definition htlist.H:1562
void next_ne() noexcept
Advances the iterator to the next filtered element (noexcept version).
constexpr bool is_empty() const noexcept
Return true if list is empty.
Definition htlist.H:523
#define TEST(name)
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
DynArray< Graph::Arc * > arcs
Definition graphpic.C:408
bool with_power
Configuration options for DOT file generation.
bool with_class
If true, set node shapes based on class field.
Digrafo::Node * search_node(Digrafo &g, const std::string &s)
Find or create a node by ID.
void load_digraph(Digrafo &g, std::istream &nodes_input, std::istream &arcs_input)
Load nodes and arcs from streams into the directed graph.
bool only_num
If true, show only the node number, not the full label.
size_t font_size
Font size for the DOT output.
void generate_dot_file(Digrafo &g, std::ostream &output)
Generate a DOT representation of the digraph.
bool with_nes
If true, include NES percentage in node labels.
Utilities for loading directed graphs from pipe-separated files.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
bool eq(const C1 &c1, const C2 &c2, Eq e=Eq())
Check equality of two containers using a predicate.
constexpr size_t MIN_NODE_FIELDS
Minimum expected columns for a node record.
std::pair< std::string, DynDlist< std::string > > Info_Nodo
Node payload: (id, fields) where fields come from the input row.
std::vector< std::string > & split(const std::string &s, const char delim, std::vector< std::string > &elems)
Split a std::string by a single delimiter character.
DynList< T > maps(const C &c, Op op)
Classic map operation.
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1164
Functor to compare nodes by their ID.