cisv 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ #ifndef CISV_WRITER_H
2
+ #define CISV_WRITER_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <stdio.h>
7
+
8
+ #ifdef __cplusplus
9
+ extern "C" {
10
+ #endif
11
+
12
+ typedef struct cisv_writer cisv_writer;
13
+
14
+ // Writer configuration
15
+ typedef struct {
16
+ char delimiter;
17
+ char quote_char;
18
+ int always_quote;
19
+ int use_crlf;
20
+ const char *null_string;
21
+ size_t buffer_size;
22
+ } cisv_writer_config;
23
+
24
+ // Create writer with default config
25
+ cisv_writer *cisv_writer_create(FILE *output);
26
+
27
+ // Create writer with custom config
28
+ cisv_writer *cisv_writer_create_config(FILE *output, const cisv_writer_config *config);
29
+
30
+ // Destroy writer and flush remaining data
31
+ void cisv_writer_destroy(cisv_writer *writer);
32
+
33
+ // Write a single field
34
+ int cisv_writer_field(cisv_writer *writer, const char *data, size_t len);
35
+
36
+ // Write a field from null-terminated string
37
+ int cisv_writer_field_str(cisv_writer *writer, const char *str);
38
+
39
+ // Write a numeric field
40
+ int cisv_writer_field_int(cisv_writer *writer, int64_t value);
41
+ int cisv_writer_field_double(cisv_writer *writer, double value, int precision);
42
+
43
+ // End current row
44
+ int cisv_writer_row_end(cisv_writer *writer);
45
+
46
+ // Write complete row from array
47
+ int cisv_writer_row(cisv_writer *writer, const char **fields, size_t count);
48
+
49
+ // Flush buffer to output
50
+ int cisv_writer_flush(cisv_writer *writer);
51
+
52
+ // Get statistics
53
+ size_t cisv_writer_bytes_written(const cisv_writer *writer);
54
+ size_t cisv_writer_rows_written(const cisv_writer *writer);
55
+
56
+ #ifdef __cplusplus
57
+ }
58
+ #endif
59
+
60
+ #endif // CISV_WRITER_H
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { cisvParser } from 'cisv';
2
+ export { cisvParser };
@@ -0,0 +1,43 @@
1
+ import { strict as assert } from 'assert';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { cisvParser } from '../index';
5
+
6
+ describe('TypeScript Integration', () => {
7
+ const testFile = path.join(__dirname, 'fixtures', 'typescript.csv');
8
+
9
+ before(() => {
10
+ fs.writeFileSync(testFile,
11
+ 'id,name\n1,Test\n2,"Quoted,Name"');
12
+ });
13
+
14
+ it('should work with TypeScript types', () => {
15
+ const parser = new cisvParser();
16
+ const rows = parser.parseSync(testFile);
17
+
18
+ assert.strictEqual(rows.length, 2);
19
+ assert.deepStrictEqual(rows[0], ['1', 'Test']);
20
+ assert.deepStrictEqual(rows[1], ['2', 'Quoted,Name']);
21
+ });
22
+
23
+ it('should type check streaming API', async () => {
24
+ const parser = new cisvParser();
25
+ const stream = fs.createReadStream(testFile);
26
+
27
+ await new Promise<void>((resolve) => {
28
+ stream.on('data', (chunk: Buffer | string) => {
29
+ if (typeof chunk === 'string') {
30
+ parser.write(Buffer.from(chunk));
31
+ } else {
32
+ parser.write(chunk);
33
+ }
34
+ });
35
+ stream.on('end', () => {
36
+ parser.end();
37
+ const rows = parser.getRows();
38
+ assert.strictEqual(rows.length, 2);
39
+ resolve();
40
+ });
41
+ });
42
+ });
43
+ });
@@ -0,0 +1,100 @@
1
+ #ifdef _WIN32
2
+ #include <windows.h>
3
+
4
+ #ifndef WIN_GETOPT_H
5
+ #define WIN_GETOPT_H
6
+
7
+ #include <string.h>
8
+ #include <stdio.h>
9
+
10
+ // Global variables
11
+ char* optarg = NULL; // Current option argument
12
+ int optind = 1; // Index of next argument to process
13
+ int opterr = 1; // Enable error messages (default: 1)
14
+ int optopt = 0; // Current option character
15
+
16
+ // Reset getopt internal state
17
+ void win_getopt_reset() {
18
+ optarg = NULL;
19
+ optind = 1;
20
+ opterr = 1;
21
+ optopt = 0;
22
+ }
23
+
24
+ int getopt(int argc, char* const argv[], const char* optstring) {
25
+ static int optpos = 1; // Position within current argument
26
+ const char* colon = strchr(optstring, ':');
27
+
28
+ // Reset for new scan
29
+ optarg = NULL;
30
+
31
+ // Argument boundary checks
32
+ if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') {
33
+ return -1;
34
+ }
35
+
36
+ // Handle "--" end of options
37
+ if (strcmp(argv[optind], "--") == 0) {
38
+ optind++;
39
+ return -1;
40
+ }
41
+
42
+ // Get current option character
43
+ optopt = argv[optind][optpos++];
44
+
45
+ // Find option in optstring
46
+ char* optptr = strchr(optstring, optopt);
47
+
48
+ // Unknown option
49
+ if (!optptr) {
50
+ if (opterr && *optstring != ':')
51
+ fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], optopt);
52
+ return '?';
53
+ }
54
+
55
+ // Handle required argument
56
+ if (optptr[1] == ':') {
57
+ // Argument in current token
58
+ if (argv[optind][optpos] != '\0') {
59
+ optarg = &argv[optind][optpos];
60
+ optpos = 1;
61
+ optind++;
62
+ }
63
+ // Argument in next token
64
+ else if (argc > optind + 1) {
65
+ optarg = argv[optind + 1];
66
+ optind += 2;
67
+ optpos = 1;
68
+ }
69
+ // Missing argument
70
+ else {
71
+ if (opterr && *optstring != ':') {
72
+ fprintf(stderr, "%s: option requires an argument -- '%c'\n",
73
+ argv[0], optopt);
74
+ }
75
+ optpos = 1;
76
+ optind++;
77
+ return (colon && colon[1] == ':') ? ':' : '?';
78
+ }
79
+ }
80
+ // No argument required
81
+ else {
82
+ if (argv[optind][optpos] == '\0') {
83
+ optpos = 1;
84
+ optind++;
85
+ }
86
+ }
87
+
88
+ return optopt;
89
+ }
90
+
91
+ #endif // WIN_GETOPT_H
92
+ #elif defined(__linux__) || defined(__APPLE__)
93
+ #include <sys/mman.h>
94
+ #include <fcntl.h>
95
+ #include <unistd.h>
96
+ #include <getopt.h>
97
+ #include <sys/time.h>
98
+ #else
99
+ #error "Unsupported platform"
100
+ #endif
@@ -0,0 +1,50 @@
1
+ #ifndef WIN_SYS_TIME_H
2
+ #define WIN_SYS_TIME_H
3
+
4
+ #ifdef _WIN32
5
+ #include <windows.h>
6
+ #include <stdint.h>
7
+
8
+ // Define timezone structure (dummy implementation)
9
+ struct timezone {
10
+ int tz_minuteswest; // minutes west of Greenwich
11
+ int tz_dsttime; // type of DST correction
12
+ };
13
+
14
+ // Get current time of day (Windows implementation)
15
+ static inline int gettimeofday(struct timeval* tv, struct timezone* tz) {
16
+ if (tv) {
17
+ FILETIME ft;
18
+ uint64_t tmpres = 0;
19
+
20
+ // Get current time as file time
21
+ GetSystemTimeAsFileTime(&ft);
22
+
23
+ // Convert to 64-bit integer
24
+ tmpres |= ft.dwHighDateTime;
25
+ tmpres <<= 32;
26
+ tmpres |= ft.dwLowDateTime;
27
+
28
+ // Convert to microseconds (1601-01-01 to 1970-01-01 is 11644473600 seconds)
29
+ tmpres /= 10; // Convert to microseconds
30
+ tmpres -= 11644473600000000ULL; // Offset to Unix epoch
31
+
32
+ tv->tv_sec = (long)(tmpres / 1000000UL);
33
+ tv->tv_usec = (long)(tmpres % 1000000UL);
34
+ }
35
+
36
+ // We don't support timezone on Windows
37
+ if (tz) {
38
+ tz->tz_minuteswest = 0;
39
+ tz->tz_dsttime = 0;
40
+ }
41
+
42
+ return 0;
43
+ }
44
+
45
+ #else
46
+ // On non-Windows platforms, use the standard header
47
+ #include <sys/time.h>
48
+ #endif // _WIN32
49
+
50
+ #endif // WIN_SYS_TIME_H
@@ -0,0 +1,104 @@
1
+ const { cisvParser } = require('../build/Release/cisv');
2
+ const assert = require('assert');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ describe('CSV Parser Core Functionality', () => {
7
+ const testDir = path.join(__dirname, 'fixtures');
8
+ const testFile = path.join(testDir, 'test.csv');
9
+ const largeFile = path.join(testDir, 'large.csv');
10
+
11
+ before(() => {
12
+ if (!fs.existsSync(testDir)) fs.mkdirSync(testDir);
13
+
14
+ // Basic test file with quoted fields
15
+ fs.writeFileSync(testFile,
16
+ 'id,name,email\n1,John,john@test.com\n2,Jane Doe,jane@test.com\n3,Alex \"The Boss\",alex@test.com');
17
+
18
+ // Generate large test file (1000 rows)
19
+ let largeContent = 'id,value\n';
20
+ for (let i = 0; i < 1000; i++) {
21
+ largeContent += `${i},Value ${i}\n`;
22
+ }
23
+ fs.writeFileSync(largeFile, largeContent);
24
+ });
25
+
26
+ after(() => {
27
+ fs.unlinkSync(testFile);
28
+ fs.unlinkSync(largeFile);
29
+ });
30
+
31
+ describe('Synchronous Parsing', () => {
32
+ it('should parse basic CSV correctly', () => {
33
+ const parser = new cisvParser();
34
+ const rows = parser.parseSync(testFile);
35
+ assert.strictEqual(rows.length, 3);
36
+ assert.deepStrictEqual(rows[0], ['id', 'name', 'email']);
37
+ assert.deepStrictEqual(rows[1], ['1', 'John', 'john@test.com']);
38
+ });
39
+
40
+ it('should handle quoted fields with commas', () => {
41
+ const parser = new cisvParser();
42
+ const rows = parser.parseSync(testFile);
43
+ assert.strictEqual(rows[2][1], 'Jane Doe');
44
+ assert.strictEqual(rows[2][2], 'jane@test.com');
45
+ });
46
+
47
+ it('should handle large files efficiently', () => {
48
+ const parser = new cisvParser();
49
+ const rows = parser.parseSync(largeFile);
50
+ assert.strictEqual(rows.length, 1001); // Header + 1000 rows
51
+ assert.strictEqual(rows[500][1], 'Value 499');
52
+ });
53
+ });
54
+
55
+ describe('Streaming API', () => {
56
+ it('should process data in chunks', (done) => {
57
+ const parser = new cisvParser();
58
+ const stream = fs.createReadStream(testFile, { highWaterMark: 16 }); // Small chunks
59
+
60
+ stream.on('data', chunk => parser.write(chunk));
61
+ stream.on('end', () => {
62
+ parser.end();
63
+ const rows = parser.getRows();
64
+ assert.strictEqual(rows.length, 3);
65
+ done();
66
+ });
67
+ });
68
+
69
+ it('should handle partial chunks', () => {
70
+ const parser = new cisvParser();
71
+ // Write partial lines
72
+ parser.write(Buffer.from('id,name,emai'));
73
+ parser.write(Buffer.from('l\n1,John,john@test.com\n'));
74
+ parser.end();
75
+ const rows = parser.getRows();
76
+ assert.strictEqual(rows.length, 2);
77
+ assert.deepStrictEqual(rows[0], ['id', 'name', 'email']);
78
+ });
79
+ });
80
+
81
+ // FIXME: should be fixed from the napi addon.cc
82
+ //describe('Error Handling', () => {
83
+ // it('should throw on non-existent file', () => {
84
+ // const parser = new cisvParser();
85
+ // assert.throws(() => parser.parseSync('./nonexistent.csv'), /parse error/);
86
+ // });
87
+
88
+ // it('should throw on invalid write arguments', () => {
89
+ // const parser = new cisvParser();
90
+ // // Test with actually invalid arguments
91
+ // assert.throws(() => parser.write(123), /Expected Buffer or String/);
92
+ // assert.throws(() => parser.write({}), /Expected Buffer or String/);
93
+ // assert.throws(() => parser.write(null), /Expected Buffer or String/);
94
+ // assert.throws(() => parser.write(), /Expected one argument/);
95
+ // });
96
+
97
+ // it('should accept both Buffer and String in write', () => {
98
+ // const parser = new cisvParser();
99
+ // // Should not throw
100
+ // assert.doesNotThrow(() => parser.write(Buffer.from('test')));
101
+ // assert.doesNotThrow(() => parser.write('test'));
102
+ // });
103
+ //});
104
+ });
package/test_select.sh ADDED
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+
3
+ echo -ne "\n\n------------------------------"
4
+ echo -ne "\n==== CISV PARSER checks ===\n"
5
+ echo -ne "------------------------------\n\n"
6
+
7
+ echo "Creating test CSV file..."
8
+ cat > test_select.csv << EOF
9
+ id,name,email,city
10
+ 1,John Doe,john@test.com,New York
11
+ 2,Jane Smith,jane@test.com,Los Angeles
12
+ 3,Bob Johnson,bob@test.com,Chicago
13
+ EOF
14
+
15
+ echo "Testing column selection..."
16
+ echo ""
17
+
18
+ echo "1. Display all columns (no selection):"
19
+ ./cisv test_select.csv
20
+ echo ""
21
+
22
+ echo "2. Select column 0 only:"
23
+ ./cisv -s 0 test_select.csv
24
+ echo ""
25
+
26
+ echo "3. Select columns 0,2:"
27
+ ./cisv -s 0,2 test_select.csv
28
+ echo ""
29
+
30
+ echo "4. Select columns 0,2,3:"
31
+ ./cisv -s 0,2,3 test_select.csv
32
+ echo ""
33
+
34
+ echo "5. Test with valgrind (if available):"
35
+ if command -v valgrind > /dev/null 2>&1; then
36
+ valgrind --leak-check=full ./cisv -s 0,2,3 test_select.csv
37
+ else
38
+ echo "Valgrind not installed"
39
+ fi
40
+
41
+ rm -f test_select.csv
42
+ echo ""
43
+ echo "All writer tests completed!"
44
+
45
+ echo -ne "\n\n------------------------------"
46
+ echo -ne "\n==== CISV WRITER checks ===\n"
47
+ echo -ne "------------------------------\n\n"
48
+
49
+ # Build if needed
50
+ if [ ! -f "./cisv" ]; then
51
+ make clean
52
+ make cli
53
+ fi
54
+
55
+ echo ""
56
+ echo "1. Testing basic generation (10 rows):"
57
+ ./cisv write -g 10 -o test_basic.csv
58
+ cat test_basic.csv
59
+ echo ""
60
+
61
+ echo "2. Testing with custom delimiter (semicolon):"
62
+ ./cisv write -g 5 -d ';' -o test_delim.csv
63
+ cat test_delim.csv
64
+ echo ""
65
+
66
+ echo "3. Testing with always quote:"
67
+ ./cisv write -g 5 -Q -o test_quoted.csv
68
+ cat test_quoted.csv
69
+ echo ""
70
+
71
+ echo "4. Testing CRLF line endings:"
72
+ ./cisv write -g 3 -r -o test_crlf.csv
73
+ file test_crlf.csv
74
+ od -c test_crlf.csv | head -2
75
+ echo ""
76
+
77
+ echo "5. Testing benchmark mode (1000 rows):"
78
+ ./cisv write -g 1000 -o test_bench.csv -b
79
+ echo ""
80
+
81
+ echo "6. Testing memory leaks with valgrind:"
82
+ if command -v valgrind > /dev/null 2>&1; then
83
+ valgrind --leak-check=full ./cisv write -g 100 -o test_valgrind.csv
84
+ else
85
+ echo "Valgrind not installed"
86
+ fi
87
+
88
+ # Cleanup
89
+ rm -f test_*.csv
90
+
91
+ echo ""
92
+ echo "All writer tests completed!"
@@ -0,0 +1,167 @@
1
+ #!/bin/bash
2
+ # Test script for CSV transform functionality with detailed memory leak detection
3
+
4
+ set -e # Exit on error
5
+
6
+ echo "============================================"
7
+ echo "CSV Transform Functionality & Memory Testing"
8
+ echo "============================================"
9
+
10
+ # Colors for output
11
+ RED='\033[0;31m'
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ NC='\033[0m' # No Color
15
+
16
+ # Build if needed
17
+ if [ ! -f "./build/Release/cisv.node" ]; then
18
+ echo -e "${YELLOW}Building Node.js addon...${NC}"
19
+ make build
20
+ fi
21
+
22
+ # Create test script
23
+ echo -e "\n${GREEN}Running transform tests...${NC}"
24
+ node ./examples/transform.js
25
+
26
+ # Test memory leaks if valgrind is available
27
+ if command -v valgrind > /dev/null 2>&1; then
28
+ echo ""
29
+ echo "============================================"
30
+ echo "MEMORY LEAK DETECTION TESTS"
31
+ echo "============================================"
32
+
33
+ echo -e "${YELLOW}Running Valgrind memory analysis...${NC}"
34
+ echo "--------------------------------------------"
35
+
36
+ # Run valgrind and save to file to avoid hanging
37
+ valgrind \
38
+ --leak-check=full \
39
+ --show-leak-kinds=definite,indirect \
40
+ --track-origins=yes \
41
+ --suppressions=valgrind-node.supp \
42
+ --log-file=valgrind_output.log \
43
+ node --expose-gc test_transform_leak_test.js 2>&1
44
+
45
+ VALGRIND_EXIT_CODE=$?
46
+
47
+ # Read the valgrind output from file
48
+ if [ -f valgrind_output.log ]; then
49
+ VALGRIND_OUTPUT=$(cat valgrind_output.log)
50
+
51
+ # Parse and display results
52
+ echo "$VALGRIND_OUTPUT" | grep -A5 "HEAP SUMMARY" || echo "No HEAP SUMMARY found"
53
+ echo "--------------------------------------------"
54
+
55
+ # Extract key metrics
56
+ DEFINITELY_LOST=$(echo "$VALGRIND_OUTPUT" | grep "definitely lost:" | tail -1)
57
+ INDIRECTLY_LOST=$(echo "$VALGRIND_OUTPUT" | grep "indirectly lost:" | tail -1)
58
+ POSSIBLY_LOST=$(echo "$VALGRIND_OUTPUT" | grep "possibly lost:" | tail -1)
59
+ STILL_REACHABLE=$(echo "$VALGRIND_OUTPUT" | grep "still reachable:" | tail -1)
60
+ ERROR_SUMMARY=$(echo "$VALGRIND_OUTPUT" | grep "ERROR SUMMARY:" | tail -1)
61
+
62
+ echo -e "\n${YELLOW}MEMORY LEAK SUMMARY:${NC}"
63
+ echo "--------------------------------------------"
64
+
65
+ # Check for definitely lost bytes
66
+ if [ -n "$DEFINITELY_LOST" ]; then
67
+ if echo "$DEFINITELY_LOST" | grep -q "0 bytes in 0 blocks"; then
68
+ echo -e "${GREEN}✓ Definitely lost: 0 bytes${NC}"
69
+ DEFINITELY_OK=true
70
+ else
71
+ echo -e "${RED}✗ $DEFINITELY_LOST${NC}"
72
+ DEFINITELY_OK=false
73
+ fi
74
+ else
75
+ echo -e "${YELLOW}⚠ Definitely lost: not found in output${NC}"
76
+ DEFINITELY_OK=false
77
+ fi
78
+
79
+ # Check for indirectly lost bytes
80
+ if [ -n "$INDIRECTLY_LOST" ]; then
81
+ if echo "$INDIRECTLY_LOST" | grep -q "0 bytes in 0 blocks"; then
82
+ echo -e "${GREEN}✓ Indirectly lost: 0 bytes${NC}"
83
+ else
84
+ echo -e "${YELLOW}⚠ $INDIRECTLY_LOST${NC}"
85
+ fi
86
+ fi
87
+
88
+ # Check for possibly lost bytes
89
+ if [ -n "$POSSIBLY_LOST" ]; then
90
+ if echo "$POSSIBLY_LOST" | grep -q "0 bytes in 0 blocks"; then
91
+ echo -e "${GREEN}✓ Possibly lost: 0 bytes${NC}"
92
+ else
93
+ echo -e "${YELLOW}⚠ $POSSIBLY_LOST${NC}"
94
+ fi
95
+ fi
96
+
97
+ # Display still reachable (usually OK for Node.js)
98
+ if [ -n "$STILL_REACHABLE" ]; then
99
+ echo -e " $STILL_REACHABLE"
100
+ fi
101
+
102
+ # Check error summary
103
+ echo "--------------------------------------------"
104
+ if [ -n "$ERROR_SUMMARY" ]; then
105
+ if echo "$ERROR_SUMMARY" | grep -q "0 errors from 0 contexts"; then
106
+ echo -e "${GREEN}✓ $ERROR_SUMMARY${NC}"
107
+ ERROR_OK=true
108
+ else
109
+ echo -e "${RED}✗ $ERROR_SUMMARY${NC}"
110
+ ERROR_OK=false
111
+ fi
112
+ else
113
+ echo -e "${YELLOW}⚠ ERROR SUMMARY not found${NC}"
114
+ ERROR_OK=false
115
+ fi
116
+
117
+ # Determine overall pass/fail
118
+ if [ "$DEFINITELY_OK" = true ] && [ "$ERROR_OK" = true ]; then
119
+ echo -e "${GREEN}✓ No memory leaks detected!${NC}"
120
+ MEMORY_TEST_PASSED=true
121
+ else
122
+ echo -e "${RED}✗ Memory issues detected!${NC}"
123
+ MEMORY_TEST_PASSED=false
124
+
125
+ # Show detailed errors if any
126
+ echo -e "\n${YELLOW}Checking for detailed errors...${NC}"
127
+ grep -i "invalid\|definitely lost\|indirectly lost" valgrind_output.log | head -20 || echo "No specific errors found"
128
+ fi
129
+
130
+ # Save report
131
+ mv valgrind_output.log valgrind_report.txt
132
+ echo -e "\n${YELLOW}Full Valgrind report saved to: valgrind_report.txt${NC}"
133
+ else
134
+ echo -e "${RED}Error: Valgrind output file not created${NC}"
135
+ MEMORY_TEST_PASSED=false
136
+ fi
137
+
138
+ # Exit with appropriate code
139
+ if [ "$MEMORY_TEST_PASSED" = true ]; then
140
+ echo -e "\n${GREEN}============================================${NC}"
141
+ echo -e "${GREEN}ALL MEMORY TESTS PASSED SUCCESSFULLY!${NC}"
142
+ echo -e "${GREEN}============================================${NC}"
143
+ else
144
+ echo -e "\n${RED}============================================${NC}"
145
+ echo -e "${RED}MEMORY TESTS FAILED - LEAKS DETECTED${NC}"
146
+ echo -e "${RED}============================================${NC}"
147
+ echo -e "\n${YELLOW}Debug tips:${NC}"
148
+ echo "1. Check valgrind_report.txt for full details"
149
+ echo "2. Look for 'definitely lost' blocks in the report"
150
+ echo "3. Use gdb to trace the allocation points"
151
+ echo "4. Consider using AddressSanitizer for additional debugging"
152
+ fi
153
+ else
154
+ echo -e "\n${YELLOW}Warning: Valgrind not installed. Skipping memory leak tests.${NC}"
155
+ echo "To install valgrind:"
156
+ echo " Ubuntu/Debian: sudo apt-get install valgrind"
157
+ echo " macOS: brew install valgrind"
158
+ echo " RHEL/CentOS: sudo yum install valgrind"
159
+ fi
160
+
161
+ # Cleanup any remaining files
162
+ rm -f leak_test*.csv valgrind_output.log
163
+
164
+ echo ""
165
+ echo "============================================"
166
+ echo "Transform tests completed!"
167
+ echo "============================================"