Skip to content

Commit 00233d1

Browse files
committed
chore: cleaned up tests
1 parent 4836b2a commit 00233d1

File tree

4 files changed

+77
-118
lines changed

4 files changed

+77
-118
lines changed

src/Parser.ts

-18
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,6 @@ function parseHeader(view: DataView): HeaderToken {
5353
? 'file'
5454
: 'directory';
5555

56-
// Const checksum = utils.extractBytes(view, HeaderOffset.CHECKSUM, HeaderSize.CHECKSUM);
57-
// if (checksum.reduce((sum, byte) => sum + byte, 0) == 0) {
58-
// console.log('Checksum null!')
59-
// console.log({
60-
// type: 'header',
61-
// filePath,
62-
// fileType,
63-
// fileMode,
64-
// fileMtime,
65-
// fileSize,
66-
// ownerGid,
67-
// ownerUid,
68-
// ownerName,
69-
// ownerUserName,
70-
// ownerGroupName,
71-
// });
72-
// }
73-
7456
return {
7557
type: 'header',
7658
filePath,

tests/Parser.test.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { tarHeaderArb, tarDataArb } from './utils';
99
describe('archive parsing', () => {
1010
test.prop([tarHeaderArb])(
1111
'should parse headers with correct state',
12-
({ header, type, details }) => {
13-
const { fullPath, mode, uid, gid } = details;
12+
({ header, stat }) => {
13+
const { type, path, uid, gid } = stat;
1414
const parser = new Parser();
1515
const token = parser.write(header);
1616

@@ -33,10 +33,9 @@ describe('archive parsing', () => {
3333
tarUtils.never('Invalid state');
3434
}
3535

36-
expect(token.filePath).toEqual(fullPath);
37-
expect(token.fileMode).toEqual(parseInt(mode, 8));
38-
expect(token.ownerUid).toEqual(parseInt(uid));
39-
expect(token.ownerGid).toEqual(parseInt(gid));
36+
expect(token.filePath).toEqual(path);
37+
expect(token.ownerUid).toEqual(uid);
38+
expect(token.ownerGid).toEqual(gid);
4039
},
4140
);
4241

tests/index.test.ts

+19-51
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,22 @@ describe('integration testing', () => {
6363

6464
const parser = new Parser();
6565
const decoder = new TextDecoder();
66-
const reconstructedVfs: any[] = [];
66+
const reconstructedVfs: Array<FileType | DirectoryType> = [];
6767
const pathStack: Map<string, any> = new Map();
68-
let currentEntry: any = undefined;
68+
let currentEntry: FileType;
6969

7070
for (const chunk of blocks) {
7171
const token = parser.write(chunk);
7272
if (token == null) continue;
7373

7474
switch (token.type) {
7575
case 'header': {
76-
let parsedEntry;
76+
let parsedEntry: FileType | DirectoryType;
7777

7878
if (token.fileType === 'file') {
7979
parsedEntry = {
8080
type: EntryType.FILE,
81-
path: token.filePath, // Path is already an array
81+
path: token.filePath,
8282
content: '',
8383
stat: {
8484
mode: token.fileMode,
@@ -105,73 +105,41 @@ describe('integration testing', () => {
105105

106106
const parentPath = path.dirname(token.filePath);
107107

108+
// If this entry is a directory, then it is pushed to the root of
109+
// the reconstructed virtual file system and into a map at the same
110+
// time. This allows us to add new children to the directory by
111+
// looking up the path in a map rather than modifying the value in
112+
// the reconstructed file system.
113+
108114
if (parentPath === '/' || parentPath === '.') {
109-
// Root-level entry
110115
reconstructedVfs.push(parsedEntry);
111116
} else {
112-
// Find or create the parent directory in reconstructedVfs
113-
let parent = pathStack.get(parentPath);
114-
if (!parent) {
115-
parent = {
116-
type: EntryType.DIRECTORY,
117-
path: parentPath,
118-
children: [],
119-
stat: {
120-
mode: 16877,
121-
uid: 0,
122-
gid: 0,
123-
size: 0,
124-
mtime: new Date(0),
125-
},
126-
};
127-
reconstructedVfs.push(parent);
128-
pathStack.set(parentPath, parent);
129-
}
130-
117+
// It is guaranteed that in a valid tar file, the parent will
118+
// always exist.
119+
const parent: DirectoryType = pathStack.get(parentPath);
131120
parent.children.push(parsedEntry);
132121
}
133122

134-
// Track directories
135-
if (token.fileType === 'directory') {
123+
if (parsedEntry.type === EntryType.DIRECTORY) {
136124
pathStack.set(token.filePath, parsedEntry);
137125
} else {
138-
currentEntry = parsedEntry;
126+
// Type narrowing doesn't work well with manually specified types
127+
currentEntry = parsedEntry as FileType;
139128
}
140129

141130
break;
142131
}
143132

144133
case 'data': {
145-
if (currentEntry) {
146-
currentEntry['content'] += decoder.decode(token.data);
147-
}
134+
// It is guaranteed that in a valid tar file, a data block will
135+
// always come after a header block for a file.
136+
currentEntry!['content'] += decoder.decode(token.data);
148137
break;
149138
}
150139
}
151140
}
152141

153-
// Console.log(printVfs(vfs));
154-
// console.log(printVfs(reconstructedVfs));
155142
expect(reconstructedVfs).toContainAllValues(vfs);
156143
},
157144
);
158145
});
159-
160-
// Function printVfs(vfs: any[], prefix: string = '') {
161-
// let output: string[] = [];
162-
//
163-
// const entriesCount = vfs.length;
164-
// vfs.forEach((entry, index) => {
165-
// const isLast = index === entriesCount - 1;
166-
// const connector = isLast ? '└── ' : '├── ';
167-
//
168-
// output.push(`${prefix}${connector}${path.basename(entry.path)} ("${entry.content}")`);
169-
//
170-
// if (entry.type === EntryType.DIRECTORY && entry.children.length > 0) {
171-
// const newPrefix = prefix + (isLast ? ' ' : '│ ');
172-
// output.push(printVfs(entry.children, newPrefix));
173-
// }
174-
// });
175-
//
176-
// return output.join('\n'); // Join accumulated lines for clean logging
177-
// }

tests/utils.ts

+53-43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FileStat } from '@/types';
1+
import { FileStat, HeaderOffset } from '@/types';
22
import fc from 'fast-check';
33
import { EntryType } from '@/types';
44
import * as tarUtils from '@/utils';
@@ -34,7 +34,7 @@ function splitHeaderData(data: Uint8Array) {
3434
}
3535

3636
const filenameArb = fc
37-
.string({ minLength: 1, maxLength: 255 })
37+
.string({ minLength: 1, maxLength: 32 })
3838
.filter((name) => !name.includes('/') && name !== '.' && name !== '..')
3939
.noShrink();
4040

@@ -116,74 +116,84 @@ const virtualFsArb = fc
116116
.noShrink();
117117

118118
const tarHeaderArb = fc
119-
.tuple(
120-
fc.option(fc.string({ minLength: 1, maxLength: 50 }), { nil: undefined }), // Optional directory
121-
fc.string({ minLength: 1, maxLength: 50 }), // Filename
122-
fc.constant('0000777\0'), // Mode (octal)
123-
fc.constant('0000000\0'), // UID (octal)
124-
fc.constant('0000000\0'), // GID (octal)
125-
fc.nat(65536), // File size (up to 1MB for files, 0 for directories)
126-
fc.constant('0000000\0'), // Mtime (octal)
127-
fc.constant(' '), // Placeholder checksum (8 spaces)
128-
fc.constantFrom('0', '5'), // Typeflag ('0' for file, '5' for directory)
129-
fc.constant(''.padEnd(100, '\0')), // USTAR format padding
130-
)
131-
.map(([dir, name, mode, uid, gid, size, , , typeflag]) => {
119+
.record({
120+
path: filenameArb,
121+
uid: fc.nat(65535),
122+
gid: fc.nat(65535),
123+
size: fc.nat(65536),
124+
typeflag: fc.constantFrom('0', '5'),
125+
})
126+
.map(({ path, uid, gid, size, typeflag }) => {
132127
const header = new Uint8Array(tarConstants.BLOCK_SIZE);
133128
const type = typeflag as '0' | '5';
134129
const encoder = new TextEncoder();
135130

136131
if (type === '5') size = 0;
137132

138-
// If nested, prepend directory name
139-
const fullPath = dir ? `${dir}/${name}` : name;
140-
141133
// Fill header fields
142-
header.set(encoder.encode(fullPath), 0);
143-
header.set(encoder.encode(mode), 100);
144-
header.set(encoder.encode(uid), 108);
145-
header.set(encoder.encode(gid), 116);
146-
header.set(encoder.encode(size.toString(8).padStart(11, '0') + '\0'), 124);
147-
header.set(encoder.encode('0000000\0'), 136); // Mtime
148-
header.set(encoder.encode(' '), 148); // Checksum placeholder
149-
header.set(encoder.encode(type), 156);
150-
header.set(encoder.encode('ustar '), 257);
134+
header.set(encoder.encode(path), HeaderOffset.FILE_NAME);
135+
header.set(encoder.encode('0000777'), HeaderOffset.FILE_MODE);
136+
header.set(
137+
encoder.encode(uid.toString(8).padStart(7, '0')),
138+
HeaderOffset.OWNER_UID,
139+
);
140+
header.set(
141+
encoder.encode(gid.toString(8).padStart(7, '0')),
142+
HeaderOffset.OWNER_GID,
143+
);
144+
header.set(
145+
encoder.encode(size.toString(8).padStart(11, '0') + '\0'),
146+
HeaderOffset.FILE_SIZE,
147+
);
148+
header.set(encoder.encode(' '), HeaderOffset.CHECKSUM);
149+
header.set(encoder.encode(type), HeaderOffset.TYPE_FLAG);
150+
header.set(encoder.encode('ustar '), HeaderOffset.USTAR_NAME);
151151

152152
// Compute and set checksum
153153
const checksum = header.reduce((sum, byte) => sum + byte, 0);
154154
header.set(
155155
encoder.encode(checksum.toString(8).padStart(6, '0') + '\0 '),
156-
148,
156+
HeaderOffset.CHECKSUM,
157157
);
158158

159-
return { header, size, type, details: { fullPath, mode, uid, gid } };
159+
return { header, stat: { type, size, path, uid, gid } };
160160
})
161161
.noShrink();
162162

163163
const tarDataArb = tarHeaderArb
164164
.chain((header) =>
165165
fc
166-
.tuple(
167-
fc.constant(header),
168-
fc.string({ minLength: header.size, maxLength: header.size }),
169-
)
170-
.map(([header, data]) => {
171-
const { header: headerBlock, size, type } = header;
166+
.record({
167+
header: fc.constant(header),
168+
data: fc.string({
169+
minLength: header.stat.size,
170+
maxLength: header.stat.size,
171+
}),
172+
})
173+
.map(({ header, data }) => {
174+
const { header: headerBlock, stat } = header;
172175
const encoder = new TextEncoder();
173176
const encodedData = encoder.encode(data);
174177

175-
// Directories don't have data
178+
// Directories don't have any data, so set their size to zero.
176179
let dataBlock: Uint8Array;
177-
if (type === '0') {
178-
const paddedSize =
179-
Math.ceil(size / tarConstants.BLOCK_SIZE) * tarConstants.BLOCK_SIZE;
180-
dataBlock = new Uint8Array(paddedSize);
181-
dataBlock.set(encodedData.subarray(0, size)); // Subarray avoids unnecessary copy
180+
if (stat.type === '0') {
181+
// Make sure the data is aligned to 512-byte chunks
182+
dataBlock = new Uint8Array(
183+
Math.ceil(stat.size / tarConstants.BLOCK_SIZE) *
184+
tarConstants.BLOCK_SIZE,
185+
);
186+
dataBlock.set(encodedData);
182187
} else {
183-
dataBlock = new Uint8Array(0); // Ensures consistent type
188+
dataBlock = new Uint8Array(0);
184189
}
185190

186-
return { header: headerBlock, data, encodedData: dataBlock, type };
191+
return {
192+
header: headerBlock,
193+
data: data,
194+
encodedData: dataBlock,
195+
type: stat.type,
196+
};
187197
}),
188198
)
189199
.noShrink();

0 commit comments

Comments
 (0)