254 lines
7.9 KiB
V
254 lines
7.9 KiB
V
module codewalker
|
|
|
|
import os
|
|
import incubaid.herolib.core.pathlib
|
|
|
|
fn test_parse_basic() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\nline1\nline2\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 1
|
|
assert fm.content['file1.txt'] == 'line1\nline2'
|
|
}
|
|
|
|
fn test_parse_multiple_files() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\nline1\n===FILE:file2.txt===\nlineA\nlineB\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 2
|
|
assert fm.content['file1.txt'] == 'line1'
|
|
assert fm.content['file2.txt'] == 'lineA\nlineB'
|
|
}
|
|
|
|
fn test_parse_empty_file_block() {
|
|
mut cw := new()
|
|
test_content := '===FILE:empty.txt===\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 1
|
|
assert fm.content['empty.txt'] == ''
|
|
}
|
|
|
|
fn test_parse_consecutive_end_and_file() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt ===\ncontent1\n===END===\n=== file2.txt===\ncontent2\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 2
|
|
assert fm.content['file1.txt'] == 'content1'
|
|
assert fm.content['file2.txt'] == 'content2'
|
|
}
|
|
|
|
fn test_parse_content_before_first_file_block() {
|
|
mut cw := new()
|
|
test_content := 'unexpected content\n===FILE:file1.txt===\ncontent\n====='
|
|
// This should ideally log an error but still parse the file
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 1
|
|
assert fm.content['file1.txt'] == 'content'
|
|
assert cw.errors.len > 0
|
|
assert cw.errors[0].message.contains('Unexpected content before first file block')
|
|
}
|
|
|
|
fn test_parse_content_after_end() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\ncontent\n===END===\nmore unexpected content'
|
|
// Implementation chooses to ignore content after END but return parsed content
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 1
|
|
assert fm.content['file1.txt'] == 'content'
|
|
}
|
|
|
|
fn test_parse_invalid_filename_line() {
|
|
mut cw := new()
|
|
test_content := '======\ncontent\n===END==='
|
|
cw.parse(test_content) or {
|
|
assert err.msg().contains('Invalid filename, < 1 chars')
|
|
return
|
|
}
|
|
assert false // Should have errored
|
|
}
|
|
|
|
fn test_parse_file_ending_without_end() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\nline1\nline2'
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 1
|
|
assert fm.content['file1.txt'] == 'line1\nline2'
|
|
}
|
|
|
|
fn test_parse_empty_content() {
|
|
mut cw := new()
|
|
test_content := ''
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 0
|
|
}
|
|
|
|
fn test_parse_only_end_at_start() {
|
|
mut cw := new()
|
|
test_content := '===END==='
|
|
cw.parse(test_content) or {
|
|
assert err.msg().contains('END found at start, not good.')
|
|
return
|
|
}
|
|
assert false // Should have errored
|
|
}
|
|
|
|
fn test_parse_mixed_file_and_filechange() {
|
|
mut cw2 := new()!
|
|
test_content2 := '===FILE:file.txt===\nfull\n===FILECHANGE:file.txt===\npartial\n===END==='
|
|
fm2 := cw2.parse(test_content2)!
|
|
assert fm2.content.len == 1
|
|
assert fm2.content_change.len == 1
|
|
assert fm2.content['file.txt'] == 'full'
|
|
assert fm2.content_change['file.txt'] == 'partial'
|
|
}
|
|
|
|
fn test_parse_empty_block_between_files() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\ncontent1\n===FILE:file2.txt===\n===END===\n===FILE:file3.txt===\ncontent3\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 3
|
|
assert fm.content['file1.txt'] == 'content1'
|
|
assert fm.content['file2.txt'] == ''
|
|
assert fm.content['file3.txt'] == 'content3'
|
|
}
|
|
|
|
fn test_parse_multiple_empty_blocks() {
|
|
mut cw := new()
|
|
test_content := '===FILE:file1.txt===\n===END===\n===FILE:file2.txt===\n===END===\n===FILE:file3.txt===\ncontent3\n===END==='
|
|
fm := cw.parse(test_content)!
|
|
assert fm.content.len == 3
|
|
assert fm.content['file1.txt'] == ''
|
|
assert fm.content['file2.txt'] == ''
|
|
assert fm.content['file3.txt'] == 'content3'
|
|
}
|
|
|
|
fn test_parse_filename_end_reserved() {
|
|
mut cw := new()
|
|
// Legacy header 'END' used as filename should error when used as header for new block
|
|
test_content := '===file1.txt===\ncontent1\n===END===\n===END===\ncontent2\n===END==='
|
|
cw.parse(test_content) or {
|
|
assert err.msg().contains("Filename 'END' is reserved.")
|
|
return
|
|
}
|
|
assert false // Should have errored
|
|
}
|
|
|
|
fn test_filemap_export_and_write() ! {
|
|
// Setup temp dir
|
|
mut tmpdir := pathlib.get_dir(
|
|
path: os.join_path(os.temp_dir(), 'cw_test')
|
|
create: true
|
|
empty: true
|
|
)!
|
|
defer {
|
|
tmpdir.delete() or {}
|
|
}
|
|
// Build a FileMap
|
|
mut fm := FileMap{
|
|
source: tmpdir.path
|
|
}
|
|
fm.set('a/b.txt', 'hello')
|
|
fm.set('c.txt', 'world')
|
|
// Export to new dir
|
|
mut dest := pathlib.get_dir(
|
|
path: os.join_path(os.temp_dir(), 'cw_out')
|
|
create: true
|
|
empty: true
|
|
)!
|
|
defer {
|
|
dest.delete() or {}
|
|
}
|
|
fm.export(dest.path)!
|
|
mut f1 := pathlib.get_file(path: os.join_path(dest.path, 'a/b.txt'))!
|
|
mut f2 := pathlib.get_file(path: os.join_path(dest.path, 'c.txt'))!
|
|
assert f1.read()! == 'hello'
|
|
assert f2.read()! == 'world'
|
|
// Overwrite via write()
|
|
fm.set('a/b.txt', 'hello2')
|
|
fm.write(dest.path)!
|
|
assert f1.read()! == 'hello2'
|
|
}
|
|
|
|
fn test_filemap_content_roundtrip() {
|
|
mut fm := FileMap{}
|
|
fm.set('x.txt', 'X')
|
|
fm.content_change['y.txt'] = 'Y'
|
|
txt := fm.content()
|
|
assert txt.contains('===FILE:x.txt===')
|
|
assert txt.contains('===FILECHANGE:y.txt===')
|
|
assert txt.contains('===END===')
|
|
}
|
|
|
|
fn test_ignore_level_scoped() ! {
|
|
// create temp dir structure
|
|
mut root := pathlib.get_dir(
|
|
path: os.join_path(os.temp_dir(), 'cw_ign_lvl')
|
|
create: true
|
|
empty: true
|
|
)!
|
|
defer { root.delete() or {} }
|
|
// subdir with its own ignore
|
|
mut sub := pathlib.get_dir(path: os.join_path(root.path, 'sub'), create: true)!
|
|
mut hero := pathlib.get_file(path: os.join_path(sub.path, '.heroignore'), create: true)!
|
|
hero.write('dist/\n')!
|
|
// files under sub/dist should be ignored
|
|
mut dist := pathlib.get_dir(path: os.join_path(sub.path, 'dist'), create: true)!
|
|
mut a1 := pathlib.get_file(path: os.join_path(dist.path, 'a.txt'), create: true)!
|
|
a1.write('A')!
|
|
// sibling sub2 with a dist, should NOT be ignored by sub's .heroignore
|
|
mut sub2 := pathlib.get_dir(path: os.join_path(root.path, 'sub2'), create: true)!
|
|
mut dist2 := pathlib.get_dir(path: os.join_path(sub2.path, 'dist'), create: true)!
|
|
mut b1 := pathlib.get_file(path: os.join_path(dist2.path, 'b.txt'), create: true)!
|
|
b1.write('B')!
|
|
// a normal file under sub should be included
|
|
mut okf := pathlib.get_file(path: os.join_path(sub.path, 'ok.txt'), create: true)!
|
|
okf.write('OK')!
|
|
|
|
mut cw := new()
|
|
mut fm := cw.filemap_get(path: root.path)!
|
|
|
|
// sub/dist/a.txt should be ignored
|
|
assert 'sub/dist/a.txt' !in fm.content.keys()
|
|
// sub/ok.txt should be included
|
|
assert fm.content['sub/ok.txt'] == 'OK'
|
|
// sub2/dist/b.txt should be included (since .heroignore is level-scoped)
|
|
assert fm.content['sub2/dist/b.txt'] == 'B'
|
|
}
|
|
|
|
fn test_ignore_level_scoped_gitignore() ! {
|
|
mut root := pathlib.get_dir(
|
|
path: os.join_path(os.temp_dir(), 'cw_ign_git')
|
|
create: true
|
|
empty: true
|
|
)!
|
|
defer { root.delete() or {} }
|
|
// root has .gitignore ignoring logs/
|
|
mut g := pathlib.get_file(path: os.join_path(root.path, '.gitignore'), create: true)!
|
|
g.write('logs/\n')!
|
|
// nested structure
|
|
mut svc := pathlib.get_dir(path: os.join_path(root.path, 'svc'), create: true)!
|
|
// this logs/ should be ignored due to root .gitignore
|
|
mut logs := pathlib.get_dir(path: os.join_path(svc.path, 'logs'), create: true)!
|
|
mut out := pathlib.get_file(path: os.join_path(logs.path, 'out.txt'), create: true)!
|
|
out.write('ignored')!
|
|
// regular file should be included
|
|
mut appf := pathlib.get_file(path: os.join_path(svc.path, 'app.txt'), create: true)!
|
|
appf.write('app')!
|
|
|
|
mut cw := new()
|
|
mut fm := cw.filemap_get(path: root.path)!
|
|
assert 'svc/logs/out.txt' !in fm.content.keys()
|
|
assert fm.content['svc/app.txt'] == 'app'
|
|
}
|
|
|
|
fn test_parse_filename_end_reserved_legacy() {
|
|
mut cw := new()
|
|
// Legacy header 'END' used as filename should error when used as header for new block
|
|
test_content := '===file1.txt===\ncontent1\n===END===\n===END===\ncontent2\n===END==='
|
|
cw.parse(test_content) or {
|
|
assert err.msg().contains("Filename 'END' is reserved.")
|
|
return
|
|
}
|
|
assert false // Should have errored
|
|
}
|