Repository navigation
Expand file tree
/
Copy pathtest_cpp_modules.py
More file actions
874 lines (721 loc) · 25.1 KB
/
Copy pathtest_cpp_modules.py
File metadata and controls
874 lines (721 loc) · 25.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
"""
Comprehensive C++20 modules testing.
Tests module interfaces, module implementations, module imports/exports,
and module partitions for graph building applications.
"""
from pathlib import Path
from typing import cast
from unittest.mock import MagicMock
import pytest
from codebase_rag.graph_updater import GraphUpdater
from codebase_rag.parser_loader import load_parsers
@pytest.fixture
def cpp_modules_project(temp_repo: Path) -> Path:
"""Create a comprehensive C++ project with modules patterns."""
project_path = temp_repo / "cpp_modules_test"
project_path.mkdir()
# Create module structure
(project_path / "modules").mkdir()
(project_path / "src").mkdir()
(project_path / "interfaces").mkdir()
return project_path
def test_basic_module_interface(
cpp_modules_project: Path,
mock_ingestor: MagicMock,
) -> None:
"""Test basic module interface declarations and exports."""
# Create module interface file
interface_file = cpp_modules_project / "interfaces" / "math_module.ixx"
interface_file.write_text(
"""
// math_module.ixx - Module interface file
export module math_operations;
import <iostream>;
import <vector>;
import <cmath>;
// Export namespace
export namespace math {
// Export basic arithmetic functions
export double add(double a, double b);
export double subtract(double a, double b);
export double multiply(double a, double b);
export double divide(double a, double b);
// Export advanced math functions
export double power(double base, double exponent);
export double square_root(double value);
export double factorial(int n);
// Export template functions
export template<typename T>
T maximum(const T& a, const T& b) {
return (a > b) ? a : b;
}
export template<typename T>
T minimum(const T& a, const T& b) {
return (a < b) ? a : b;
}
// Export template class
export template<typename T>
class Calculator {
private:
T accumulator_;
public:
Calculator() : accumulator_(T{}) {}
void reset() { accumulator_ = T{}; }
T get_result() const { return accumulator_; }
Calculator& add(const T& value) {
accumulator_ += value;
return *this;
}
Calculator& multiply(const T& value) {
accumulator_ *= value;
return *this;
}
Calculator& apply(std::function<T(T)> func) {
accumulator_ = func(accumulator_);
return *this;
}
};
// Export constants
export constexpr double PI = 3.14159265359;
export constexpr double E = 2.71828182846;
// Export enums
export enum class Operation {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
POWER
};
// Export structs
export struct Point2D {
double x, y;
Point2D(double x = 0.0, double y = 0.0) : x(x), y(y) {}
double distance_to(const Point2D& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
Point2D operator+(const Point2D& other) const {
return Point2D(x + other.x, y + other.y);
}
};
// Export class with complex features
export class MathProcessor {
private:
std::vector<double> history_;
Operation last_operation_;
public:
MathProcessor() : last_operation_(Operation::ADD) {}
double process(double a, double b, Operation op);
void clear_history() { history_.clear(); }
const std::vector<double>& get_history() const { return history_; }
Operation get_last_operation() const { return last_operation_; }
// Template member function
template<typename Container>
double sum_container(const Container& container) {
double sum = 0.0;
for (const auto& value : container) {
sum += static_cast<double>(value);
}
history_.push_back(sum);
return sum;
}
};
// Export type aliases
export using FloatCalculator = Calculator<float>;
export using DoubleCalculator = Calculator<double>;
export using IntCalculator = Calculator<int>;
// Export function templates with concepts (if available)
export template<typename T>
requires std::is_arithmetic_v<T>
T safe_divide(T numerator, T denominator) {
if (denominator == T{}) {
throw std::invalid_argument("Division by zero");
}
return numerator / denominator;
}
}
// Non-exported internal functions (module implementation details)
namespace math::internal {
double validate_input(double value) {
return std::isfinite(value) ? value : 0.0;
}
template<typename T>
T clamp(const T& value, const T& min_val, const T& max_val) {
return std::max(min_val, std::min(value, max_val));
}
}
"""
)
# Create implementation file
impl_file = cpp_modules_project / "src" / "math_module.cpp"
impl_file.write_text(
"""
// math_module.cpp - Module implementation file
module math_operations;
import <stdexcept>;
import <cmath>;
// Implement exported functions
namespace math {
double add(double a, double b) {
return internal::validate_input(a) + internal::validate_input(b);
}
double subtract(double a, double b) {
return internal::validate_input(a) - internal::validate_input(b);
}
double multiply(double a, double b) {
return internal::validate_input(a) * internal::validate_input(b);
}
double divide(double a, double b) {
double validated_a = internal::validate_input(a);
double validated_b = internal::validate_input(b);
if (std::abs(validated_b) < 1e-10) {
throw std::invalid_argument("Division by zero");
}
return validated_a / validated_b;
}
double power(double base, double exponent) {
return std::pow(internal::validate_input(base),
internal::validate_input(exponent));
}
double square_root(double value) {
double validated = internal::validate_input(value);
if (validated < 0.0) {
throw std::invalid_argument("Square root of negative number");
}
return std::sqrt(validated);
}
double factorial(int n) {
if (n < 0) {
throw std::invalid_argument("Factorial of negative number");
}
double result = 1.0;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
// Implement MathProcessor methods
double MathProcessor::process(double a, double b, Operation op) {
double result = 0.0;
last_operation_ = op;
switch (op) {
case Operation::ADD:
result = add(a, b);
break;
case Operation::SUBTRACT:
result = subtract(a, b);
break;
case Operation::MULTIPLY:
result = multiply(a, b);
break;
case Operation::DIVIDE:
result = divide(a, b);
break;
case Operation::POWER:
result = power(a, b);
break;
}
history_.push_back(result);
return result;
}
}
"""
)
parsers, queries = load_parsers()
updater = GraphUpdater(
ingestor=mock_ingestor,
repo_path=cpp_modules_project,
parsers=parsers,
queries=queries,
)
updater.run()
project_name = cpp_modules_project.name
# Expected module-related classes and functions
expected_classes = [
f"{project_name}.math_module.Calculator",
f"{project_name}.math_module.MathProcessor",
f"{project_name}.math_module.Point2D",
]
expected_functions = [
f"{project_name}.math_module.add",
f"{project_name}.math_module.subtract",
f"{project_name}.math_module.multiply",
f"{project_name}.math_module.divide",
f"{project_name}.math_module.power",
f"{project_name}.math_module.square_root",
f"{project_name}.math_module.factorial",
]
# Get all Class node creation calls
class_calls = [
call
for call in mock_ingestor.ensure_node_batch.call_args_list
if call[0][0] == "Class"
]
created_classes = {call[0][1]["qualified_name"] for call in class_calls}
# Verify expected classes were created
found_classes = [cls for cls in expected_classes if cls in created_classes]
assert len(found_classes) >= 1, (
f"Expected at least 1 module class, found {len(found_classes)}: {found_classes}"
)
# Get all Function node creation calls
function_calls = [
call
for call in mock_ingestor.ensure_node_batch.call_args_list
if call[0][0] == "Function"
]
created_functions = {call[0][1]["qualified_name"] for call in function_calls}
# Verify at least some expected functions were created
missing_functions = set(expected_functions) - created_functions
assert not missing_functions, (
f"Missing expected functions: {sorted(list(missing_functions))}"
)
def test_module_partitions(
cpp_modules_project: Path,
mock_ingestor: MagicMock,
) -> None:
"""Test module partitions and internal module structure."""
# Create primary module interface
primary_interface = cpp_modules_project / "interfaces" / "data_structures.ixx"
primary_interface.write_text(
"""
// data_structures.ixx - Primary module interface
export module data_structures;
// Export partitions
export import :containers;
export import :algorithms;
export import :iterators;
// Main module exports
export namespace ds {
// Unified interface for all data structures
template<typename T>
concept DataStructure = requires(T t) {
{ t.size() } -> std::convertible_to<std::size_t>;
{ t.empty() } -> std::convertible_to<bool>;
{ t.clear() } -> std::same_as<void>;
};
// Factory functions
export template<typename T>
auto make_dynamic_array(std::initializer_list<T> init) {
return containers::DynamicArray<T>(init);
}
export template<typename T>
auto make_linked_list(std::initializer_list<T> init) {
return containers::LinkedList<T>(init);
}
// Utility functions
export template<DataStructure Container, typename Predicate>
void process_if(Container& container, Predicate pred) {
algorithms::for_each_if(container, pred);
}
}
"""
)
# Create container partition
container_partition = cpp_modules_project / "modules" / "containers.ixx"
container_partition.write_text(
"""
// containers.ixx - Container partition
export module data_structures:containers;
import <memory>;
import <initializer_list>;
import <stdexcept>;
export namespace ds::containers {
// Dynamic array implementation
template<typename T>
class DynamicArray {
private:
std::unique_ptr<T[]> data_;
std::size_t size_;
std::size_t capacity_;
void resize_if_needed() {
if (size_ >= capacity_) {
std::size_t new_capacity = capacity_ * 2;
auto new_data = std::make_unique<T[]>(new_capacity);
for (std::size_t i = 0; i < size_; ++i) {
new_data[i] = std::move(data_[i]);
}
data_ = std::move(new_data);
capacity_ = new_capacity;
}
}
public:
DynamicArray() : size_(0), capacity_(4) {
data_ = std::make_unique<T[]>(capacity_);
}
DynamicArray(std::initializer_list<T> init)
: size_(init.size()), capacity_(std::max(std::size_t{4}, init.size())) {
data_ = std::make_unique<T[]>(capacity_);
std::size_t i = 0;
for (const auto& item : init) {
data_[i++] = item;
}
}
void push_back(const T& value) {
resize_if_needed();
data_[size_++] = value;
}
void push_back(T&& value) {
resize_if_needed();
data_[size_++] = std::move(value);
}
T& operator[](std::size_t index) {
if (index >= size_) throw std::out_of_range("Index out of range");
return data_[index];
}
const T& operator[](std::size_t index) const {
if (index >= size_) throw std::out_of_range("Index out of range");
return data_[index];
}
std::size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
void clear() { size_ = 0; }
// Iterator support
T* begin() { return data_.get(); }
T* end() { return data_.get() + size_; }
const T* begin() const { return data_.get(); }
const T* end() const { return data_.get() + size_; }
};
// Linked list implementation
template<typename T>
class LinkedList {
private:
struct Node {
T data;
std::unique_ptr<Node> next;
Node(const T& value) : data(value) {}
Node(T&& value) : data(std::move(value)) {}
};
std::unique_ptr<Node> head_;
std::size_t size_;
public:
LinkedList() : size_(0) {}
LinkedList(std::initializer_list<T> init) : size_(0) {
for (const auto& item : init) {
push_back(item);
}
}
void push_front(const T& value) {
auto new_node = std::make_unique<Node>(value);
new_node->next = std::move(head_);
head_ = std::move(new_node);
++size_;
}
void push_back(const T& value) {
auto new_node = std::make_unique<Node>(value);
if (!head_) {
head_ = std::move(new_node);
} else {
Node* current = head_.get();
while (current->next) {
current = current->next.get();
}
current->next = std::move(new_node);
}
++size_;
}
void pop_front() {
if (head_) {
head_ = std::move(head_->next);
--size_;
}
}
std::size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
void clear() {
head_.reset();
size_ = 0;
}
// Simple iterator
class iterator {
Node* current_;
public:
iterator(Node* node) : current_(node) {}
T& operator*() { return current_->data; }
iterator& operator++() {
if (current_) current_ = current_->next.get();
return *this;
}
bool operator!=(const iterator& other) const {
return current_ != other.current_;
}
};
iterator begin() { return iterator(head_.get()); }
iterator end() { return iterator(nullptr); }
};
}
"""
)
# Create algorithms partition
algorithms_partition = cpp_modules_project / "modules" / "algorithms.ixx"
algorithms_partition.write_text(
"""
// algorithms.ixx - Algorithms partition
export module data_structures:algorithms;
import <functional>;
import <type_traits>;
export namespace ds::algorithms {
// Generic algorithms for data structures
template<typename Container, typename Function>
void for_each(Container& container, Function func) {
for (auto& item : container) {
func(item);
}
}
template<typename Container, typename Predicate>
void for_each_if(Container& container, Predicate pred) {
for (auto& item : container) {
if (pred(item)) {
// Process item that matches predicate
std::cout << "Processing: " << item << std::endl;
}
}
}
template<typename Container, typename T>
bool contains(const Container& container, const T& value) {
for (const auto& item : container) {
if (item == value) {
return true;
}
}
return false;
}
template<typename Container, typename Predicate>
std::size_t count_if(const Container& container, Predicate pred) {
std::size_t count = 0;
for (const auto& item : container) {
if (pred(item)) {
++count;
}
}
return count;
}
template<typename Container, typename T>
void fill(Container& container, const T& value) {
for (auto& item : container) {
item = value;
}
}
template<typename Container, typename Transformer>
void transform(Container& container, Transformer transformer) {
for (auto& item : container) {
item = transformer(item);
}
}
// Specialized algorithms
template<typename Container>
requires std::is_arithmetic_v<typename Container::value_type>
auto sum(const Container& container) {
using T = typename Container::value_type;
T result{};
for (const auto& item : container) {
result += item;
}
return result;
}
template<typename Container>
requires std::is_arithmetic_v<typename Container::value_type>
auto average(const Container& container) {
if (container.empty()) return typename Container::value_type{};
return sum(container) / static_cast<typename Container::value_type>(container.size());
}
}
"""
)
parsers, queries = load_parsers()
updater = GraphUpdater(
ingestor=mock_ingestor,
repo_path=cpp_modules_project,
parsers=parsers,
queries=queries,
)
updater.run()
project_name = cpp_modules_project.name
# Expected partition-related classes and functions
expected_classes = [
f"{project_name}.containers.DynamicArray",
f"{project_name}.containers.LinkedList",
]
# Get all Class node creation calls
class_calls = [
call
for call in mock_ingestor.ensure_node_batch.call_args_list
if call[0][0] == "Class"
]
created_classes = {call[0][1]["qualified_name"] for call in class_calls}
# Verify expected classes were created
found_classes = [cls for cls in expected_classes if cls in created_classes]
assert found_classes, (
f"Expected at least 1 partition class, found {len(found_classes)}: {found_classes}"
)
def test_module_imports_usage(
cpp_modules_project: Path,
mock_ingestor: MagicMock,
) -> None:
"""Test module imports and usage patterns."""
# Create a file that uses the modules
usage_file = cpp_modules_project / "src" / "module_usage.cpp"
usage_file.write_text(
"""
// module_usage.cpp - Using imported modules
import math_operations;
import data_structures;
import <iostream>;
import <vector>;
import <string>;
// Using imported modules
void testMathOperations() {
using namespace math;
std::cout << "=== Testing Math Operations Module ===" << std::endl;
// Test basic operations
double result1 = add(10.5, 5.3);
double result2 = multiply(result1, 2.0);
double result3 = power(result2, 0.5);
std::cout << "Calculations: " << result1 << ", " << result2 << ", " << result3 << std::endl;
// Test template functions
auto max_int = maximum(42, 38);
auto min_double = minimum(3.14, 2.71);
std::cout << "Max int: " << max_int << ", Min double: " << min_double << std::endl;
// Test Calculator class
DoubleCalculator calc;
double calc_result = calc.add(10.0).multiply(2.0).add(5.0).get_result();
std::cout << "Calculator result: " << calc_result << std::endl;
// Test Point2D
Point2D p1(0.0, 0.0);
Point2D p2(3.0, 4.0);
double distance = p1.distance_to(p2);
Point2D p3 = p1 + p2;
std::cout << "Distance: " << distance << ", Sum point: (" << p3.x << ", " << p3.y << ")" << std::endl;
// Test MathProcessor
MathProcessor processor;
double proc_result1 = processor.process(10.0, 5.0, Operation::ADD);
double proc_result2 = processor.process(proc_result1, 3.0, Operation::MULTIPLY);
std::cout << "Processor results: " << proc_result1 << ", " << proc_result2 << std::endl;
std::cout << "History size: " << processor.get_history().size() << std::endl;
// Test concepts and safe operations
try {
double safe_result = safe_divide(10.0, 2.0);
std::cout << "Safe division: " << safe_result << std::endl;
// This should throw
double unsafe_result = safe_divide(10.0, 0.0);
} catch (const std::exception& e) {
std::cout << "Caught expected exception: " << e.what() << std::endl;
}
}
void testDataStructures() {
using namespace ds;
using namespace ds::containers;
using namespace ds::algorithms;
std::cout << "=== Testing Data Structures Module ===" << std::endl;
// Test DynamicArray
auto array = make_dynamic_array({1, 2, 3, 4, 5});
array.push_back(6);
array.push_back(7);
std::cout << "Dynamic array size: " << array.size() << std::endl;
std::cout << "Array contents: ";
for (const auto& item : array) {
std::cout << item << " ";
}
std::cout << std::endl;
// Test LinkedList
auto list = make_linked_list<std::string>({"hello", "world", "from", "modules"});
list.push_front("say");
std::cout << "Linked list size: " << list.size() << std::endl;
std::cout << "List contents: ";
for (const auto& item : list) {
std::cout << item << " ";
}
std::cout << std::endl;
// Test algorithms
std::cout << "Contains 'world': " << contains(list, std::string("world")) << std::endl;
auto count = count_if(array, [](int x) { return x % 2 == 0; });
std::cout << "Even numbers in array: " << count << std::endl;
auto array_sum = sum(array);
auto array_avg = average(array);
std::cout << "Array sum: " << array_sum << ", average: " << array_avg << std::endl;
// Test with concept
process_if(array, [](int x) { return x > 3; });
}
void testModuleInterop() {
std::cout << "=== Testing Module Interoperability ===" << std::endl;
// Use math module with data structures
auto numbers = ds::make_dynamic_array<double>({1.0, 2.0, 3.0, 4.0, 5.0});
// Apply math operations to container
ds::algorithms::transform(numbers, [](double x) {
return math::power(x, 2.0); // Square each element
});
std::cout << "Squared numbers: ";
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// Use math calculator with container data
math::DoubleCalculator calc;
for (const auto& num : numbers) {
calc.add(num);
}
std::cout << "Sum of squared numbers: " << calc.get_result() << std::endl;
// Complex interop example
auto points = ds::make_dynamic_array<math::Point2D>({
{0.0, 0.0}, {1.0, 1.0}, {2.0, 2.0}, {3.0, 3.0}
});
math::Point2D origin(0.0, 0.0);
std::cout << "Distances from origin: ";
for (const auto& point : points) {
double dist = origin.distance_to(point);
std::cout << dist << " ";
}
std::cout << std::endl;
}
// Global module usage demonstration
void demonstrateModuleUsage() {
testMathOperations();
testDataStructures();
testModuleInterop();
std::cout << "=== Module Usage Demonstration Complete ===" << std::endl;
}
// Module feature showcase
void showcaseModuleFeatures() {
std::cout << "=== Module Features Showcase ===" << std::endl;
// Demonstrate clean interfaces
std::cout << "1. Clean module interfaces with export/import" << std::endl;
std::cout << "2. Module partitions for code organization" << std::endl;
std::cout << "3. Template exports and instantiation" << std::endl;
std::cout << "4. Concept integration with modules" << std::endl;
std::cout << "5. Cross-module functionality" << std::endl;
std::cout << "6. Encapsulation of implementation details" << std::endl;
demonstrateModuleUsage();
}
"""
)
parsers, queries = load_parsers()
updater = GraphUpdater(
ingestor=mock_ingestor,
repo_path=cpp_modules_project,
parsers=parsers,
queries=queries,
)
updater.run()
# Verify comprehensive module usage coverage
all_relationships = cast(
MagicMock, mock_ingestor.ensure_relationship_batch
).call_args_list
call_relationships = [c for c in all_relationships if c.args[1] == "CALLS"]
imports_relationships = [c for c in all_relationships if c.args[1] == "IMPORTS"]
# Should have module import relationships
[
call
for call in imports_relationships
if "module_usage" in call.args[0][2]
and any(
module_name in str(call.args[2])
for module_name in ["math_operations", "data_structures"]
)
]
# Should have calls to module functions
module_function_calls = [
call for call in call_relationships if "module_usage" in call.args[0][2]
]
assert len(module_function_calls) >= 5, (
f"Expected at least 5 module function calls, found {len(module_function_calls)}"
)