Skip to content

Commit c06e659

Browse files
committed
chore(release): Version auf 1.16.0 erhöhen & Changelog aktualisieren (2025-10-20) 🚀
- test: Füge Async-File-Operations- und Cache-Configuration-Tests hinzu 🧪 - fix: Korrigiere readLargeFileAsync — sammle Chunks als strings und join statt Buffer.concat 🔧 - chore: Aktualisiere package.json Version und setze Release-Datum im CHANGELOG 📝
1 parent fab3cf3 commit c06e659

File tree

5 files changed

+209
-8
lines changed

5 files changed

+209
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ All notable changes to the "magento-log-viewer" extension will be documented in
44

55
## Next release
66

7-
### [1.16.0] - TBD
7+
### [1.16.0] - 2025-10-20
88

99
- perf: Implemented dynamic cache configuration based on available system memory
1010
- perf: Added intelligent cache size management with automatic optimization under memory pressure
@@ -21,6 +21,8 @@ All notable changes to the "magento-log-viewer" extension will be documented in
2121
- fix: Improved memory usage estimation and monitoring for cached file contents
2222
- fix: Eliminated redundant `pathExists` function implementations across modules
2323
- fix: Consolidated all path existence checks to use centralized helpers functions
24+
- test: Added comprehensive test coverage for new cache configuration options
25+
- test: Added async file operations test suite with large file handling validation
2426

2527
---
2628

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "magento-log-viewer",
33
"displayName": "Magento Log Viewer",
44
"description": "A Visual Studio Code extension to view and manage Magento log files.",
5-
"version": "1.15.0",
5+
"version": "1.16.0",
66
"publisher": "MathiasElle",
77
"icon": "resources/logo.png",
88
"repository": {

src/helpers.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -631,25 +631,23 @@ export async function getCachedFileContentAsync(filePath: string): Promise<strin
631631
// Stream-based reading for very large files
632632
async function readLargeFileAsync(filePath: string): Promise<string> {
633633
return new Promise((resolve, reject) => {
634-
const chunks: Buffer[] = [];
634+
const chunks: string[] = [];
635635
const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
636636

637-
stream.on('data', (chunk: Buffer) => {
637+
stream.on('data', (chunk: string) => {
638638
chunks.push(chunk);
639639
});
640640

641641
stream.on('end', () => {
642-
resolve(Buffer.concat(chunks).toString('utf8'));
642+
resolve(chunks.join(''));
643643
});
644644

645645
stream.on('error', (error) => {
646646
console.error(`Error reading large file ${filePath}:`, error);
647647
reject(error);
648648
});
649649
});
650-
}
651-
652-
// Enhanced file content caching function (synchronous - for compatibility)
650+
}// Enhanced file content caching function (synchronous - for compatibility)
653651
export function getCachedFileContent(filePath: string): string | null {
654652
try {
655653
// Check if file exists first

src/test/asyncOperations.test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as assert from 'assert';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import * as os from 'os';
5+
import { pathExists, pathExistsAsync, getCachedFileContentAsync, getCachedFileContent } from '../helpers';
6+
7+
suite('Async File Operations Test Suite', () => {
8+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'magento-async-test-'));
9+
let testFilePath: string;
10+
11+
setup(() => {
12+
// Create a test file for each test
13+
testFilePath = path.join(tempDir, 'async-test.log');
14+
fs.writeFileSync(testFilePath, 'Test content for async operations');
15+
});
16+
17+
teardown(() => {
18+
// Clean up test file after each test
19+
if (fs.existsSync(testFilePath)) {
20+
fs.unlinkSync(testFilePath);
21+
}
22+
});
23+
24+
suiteTeardown(() => {
25+
// Clean up temp directory
26+
try {
27+
if (fs.existsSync(tempDir)) {
28+
const files = fs.readdirSync(tempDir);
29+
files.forEach(file => {
30+
fs.unlinkSync(path.join(tempDir, file));
31+
});
32+
fs.rmdirSync(tempDir);
33+
}
34+
} catch (err) {
35+
console.error('Failed to clean up test directory:', err);
36+
}
37+
});
38+
39+
test('pathExistsAsync should work correctly for existing files', async () => {
40+
const exists = await pathExistsAsync(testFilePath);
41+
assert.strictEqual(exists, true, 'Should return true for existing file');
42+
});
43+
44+
test('pathExistsAsync should work correctly for non-existing files', async () => {
45+
const nonExistentPath = path.join(tempDir, 'non-existent.log');
46+
const exists = await pathExistsAsync(nonExistentPath);
47+
assert.strictEqual(exists, false, 'Should return false for non-existing file');
48+
});
49+
50+
test('pathExists and pathExistsAsync should return same results', async () => {
51+
const syncResult = pathExists(testFilePath);
52+
const asyncResult = await pathExistsAsync(testFilePath);
53+
assert.strictEqual(syncResult, asyncResult, 'Sync and async should return same result');
54+
55+
const nonExistentPath = path.join(tempDir, 'non-existent.log');
56+
const syncResultFalse = pathExists(nonExistentPath);
57+
const asyncResultFalse = await pathExistsAsync(nonExistentPath);
58+
assert.strictEqual(syncResultFalse, asyncResultFalse, 'Sync and async should return same result for non-existing file');
59+
});
60+
61+
test('getCachedFileContentAsync should read file content correctly', async () => {
62+
const content = await getCachedFileContentAsync(testFilePath);
63+
assert.strictEqual(content, 'Test content for async operations', 'Should read file content correctly');
64+
});
65+
66+
test('getCachedFileContentAsync should handle non-existent files gracefully', async () => {
67+
const nonExistentPath = path.join(tempDir, 'non-existent.log');
68+
const content = await getCachedFileContentAsync(nonExistentPath);
69+
assert.strictEqual(content, null, 'Should return null for non-existent files');
70+
});
71+
72+
test('getCachedFileContentAsync should handle large files with streaming', async () => {
73+
// Create a large test file (>50MB)
74+
const largeFilePath = path.join(tempDir, 'large-async-test.log');
75+
const largeContent = 'x'.repeat(51 * 1024 * 1024); // 51MB of content
76+
77+
fs.writeFileSync(largeFilePath, largeContent);
78+
79+
try {
80+
const content = await getCachedFileContentAsync(largeFilePath);
81+
assert.strictEqual(content, largeContent, 'Should read large file content correctly using streaming');
82+
} finally {
83+
// Clean up large file
84+
if (fs.existsSync(largeFilePath)) {
85+
fs.unlinkSync(largeFilePath);
86+
}
87+
}
88+
});
89+
90+
test('getCachedFileContentAsync should cache content correctly', async () => {
91+
// First call - should read from file
92+
const firstCall = await getCachedFileContentAsync(testFilePath);
93+
assert.strictEqual(firstCall, 'Test content for async operations', 'First call should read file content');
94+
95+
// Second call without file modification - should return cached content
96+
const secondCall = await getCachedFileContentAsync(testFilePath);
97+
assert.strictEqual(secondCall, 'Test content for async operations', 'Second call should return cached content');
98+
});
99+
100+
test('getCachedFileContentAsync performance should be reasonable', async () => {
101+
const startTime = Date.now();
102+
103+
// Read the same file multiple times
104+
for (let i = 0; i < 10; i++) {
105+
await getCachedFileContentAsync(testFilePath);
106+
}
107+
108+
const endTime = Date.now();
109+
const duration = endTime - startTime;
110+
111+
// Should complete quickly due to caching
112+
assert.ok(duration < 1000, `Multiple async reads should complete quickly, took ${duration}ms`);
113+
});
114+
115+
test('Async and sync getCachedFileContent should return same results', async () => {
116+
const syncContent = getCachedFileContent(testFilePath);
117+
const asyncContent = await getCachedFileContentAsync(testFilePath);
118+
119+
assert.strictEqual(syncContent, asyncContent, 'Sync and async content reading should return same results');
120+
});
121+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as assert from 'assert';
2+
import * as vscode from 'vscode';
3+
import { getCacheStatistics, clearFileContentCache, optimizeCacheSize } from '../helpers';
4+
5+
suite('Cache Configuration Test Suite', () => {
6+
7+
setup(() => {
8+
// Clear cache before each test
9+
clearFileContentCache();
10+
});
11+
12+
teardown(() => {
13+
// Reset configuration after each test using Global target
14+
const config = vscode.workspace.getConfiguration('magentoLogViewer');
15+
config.update('cacheMaxFiles', undefined, vscode.ConfigurationTarget.Global);
16+
config.update('cacheMaxFileSize', undefined, vscode.ConfigurationTarget.Global);
17+
config.update('enableCacheStatistics', undefined, vscode.ConfigurationTarget.Global);
18+
clearFileContentCache();
19+
});
20+
21+
test('Cache should use default configuration when user settings are 0', () => {
22+
const stats = getCacheStatistics();
23+
24+
// Should have reasonable defaults
25+
assert.ok(stats.maxSize >= 20 && stats.maxSize <= 100,
26+
`Max size should be between 20-100, got ${stats.maxSize}`);
27+
assert.ok(stats.maxFileSize >= 1024 * 1024,
28+
`Max file size should be at least 1MB, got ${stats.maxFileSize}`);
29+
});
30+
31+
test('Cache should respect user configuration when set', async () => {
32+
const config = vscode.workspace.getConfiguration('magentoLogViewer');
33+
34+
// Set custom values using Global configuration target since no workspace is available in tests
35+
await config.update('cacheMaxFiles', 75, vscode.ConfigurationTarget.Global);
36+
await config.update('cacheMaxFileSize', 8, vscode.ConfigurationTarget.Global);
37+
38+
// Clear cache to pick up new config
39+
clearFileContentCache();
40+
41+
const stats = getCacheStatistics();
42+
assert.strictEqual(stats.maxSize, 75, 'Should use custom max files setting');
43+
assert.strictEqual(stats.maxFileSize, 8 * 1024 * 1024, 'Should use custom max file size setting');
44+
});
45+
46+
test('Cache statistics should include memory usage information', () => {
47+
const stats = getCacheStatistics();
48+
49+
assert.ok(typeof stats.size === 'number', 'Size should be a number');
50+
assert.ok(typeof stats.maxSize === 'number', 'Max size should be a number');
51+
assert.ok(typeof stats.maxFileSize === 'number', 'Max file size should be a number');
52+
assert.ok(typeof stats.memoryUsage === 'string', 'Memory usage should be a string');
53+
assert.ok(stats.memoryUsage.includes('MB'), 'Memory usage should include MB unit');
54+
});
55+
56+
test('optimizeCacheSize should handle memory pressure', () => {
57+
// This is hard to test without mocking process.memoryUsage()
58+
// But we can at least ensure the function doesn't crash
59+
assert.doesNotThrow(() => {
60+
optimizeCacheSize();
61+
}, 'optimizeCacheSize should not throw errors');
62+
});
63+
64+
test('Cache configuration should have reasonable bounds', async () => {
65+
const config = vscode.workspace.getConfiguration('magentoLogViewer');
66+
67+
// Test edge cases using Global configuration
68+
await config.update('cacheMaxFiles', 1000, vscode.ConfigurationTarget.Global); // Too high
69+
await config.update('cacheMaxFileSize', 200, vscode.ConfigurationTarget.Global); // Too high
70+
71+
clearFileContentCache();
72+
73+
const stats = getCacheStatistics();
74+
75+
// User settings override automatic limits, so we should get the user values
76+
// The test should verify that the configuration accepts user values
77+
assert.strictEqual(stats.maxSize, 1000, 'Should accept user setting for max files even if high');
78+
assert.strictEqual(stats.maxFileSize, 200 * 1024 * 1024, 'Should accept user setting for max file size even if high');
79+
});
80+
});

0 commit comments

Comments
 (0)