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.
- package/.github/workflows/ci.yml +158 -0
- package/.github/workflows/release.yml +167 -0
- package/Dockerfile +63 -0
- package/LICENSE +7 -0
- package/Makefile +160 -0
- package/README.md +249 -0
- package/SIMD_benchmarks.md +658 -0
- package/benchmark/benchmark.js +287 -0
- package/benchmark_cli_reader.sh +236 -0
- package/benchmark_cli_writer.sh +280 -0
- package/binding.gyp +57 -0
- package/debug-addon.js +64 -0
- package/examples/basic-parse.js +65 -0
- package/examples/large-file.js +35 -0
- package/examples/transform.js +152 -0
- package/examples/typescript.ts +38 -0
- package/index.d.ts +336 -0
- package/install_benchmark_deps.sh +156 -0
- package/package.json +47 -0
- package/run_benchmarks.sh +53 -0
- package/src/cisv_addon.cc +614 -0
- package/src/cisv_parser.c +988 -0
- package/src/cisv_parser.h +55 -0
- package/src/cisv_simd.h +53 -0
- package/src/cisv_transformer.c +537 -0
- package/src/cisv_transformer.h +145 -0
- package/src/cisv_writer.c +535 -0
- package/src/cisv_writer.h +60 -0
- package/src/index.ts +2 -0
- package/src/test/typescript.test.ts +43 -0
- package/src/win_getopt.h +100 -0
- package/src/win_sys_time.h +50 -0
- package/test/basic.test.js +104 -0
- package/test_select.sh +92 -0
- package/test_transform.sh +167 -0
- package/test_transform_leak_test.js +94 -0
- package/tsconfig.json +17 -0
- package/types/cisv.d.ts +8 -0
- package/valgrind-node.supp +69 -0
|
@@ -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,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
|
+
});
|
package/src/win_getopt.h
ADDED
|
@@ -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 "============================================"
|