WIP 177 보충 2: 28 의 장면 · 옵션 19개를 실제 rsync 와 비교하는 시험 · 가운데 "/**/" 는 폴더 하나 이상 (맨 앞 "**/" 만 0개) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>next3
parent
cda2a30664
commit
09fbe0337a
|
|
@ -165,8 +165,9 @@ class RsyncFilterRule {
|
||||||
while (i + 1 < p.length && p[i + 1] == '*') {
|
while (i + 1 < p.length && p[i + 1] == '*') {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
// rsync wildmatch: "**/" 는 폴더 0개에도 맞는다 ("**/deep/**" 가 맨 위 deep/x 에도)
|
// rsync wildmatch: 패턴 맨 앞의 "**/" 는 폴더 0개에도 맞는다 ("**/deep/**" 가 맨 위 deep/x 에도).
|
||||||
if (i + 1 < p.length && p[i + 1] == '/') {
|
// 가운데의 "/**/" 는 폴더 하나 이상 ("foo/**/bar" 는 foo/bar 에 맞지 않음 - 실제 rsync 확인)
|
||||||
|
if (b.isEmpty && i + 1 < p.length && p[i + 1] == '/') {
|
||||||
b.write('(?:.*/)?');
|
b.write('(?:.*/)?');
|
||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -195,4 +195,87 @@ void main() {
|
||||||
expect(await preview(opts), await real(opts));
|
expect(await preview(opts), await real(opts));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 28 (10/19) 이 비교에 쓴 장면과 옵션 19개 그대로 - 옵션을 rsync 에 그대로 (+ -8 -n -i), 미리 보기는 그 옵션의 거르기 · 지우기
|
||||||
|
group('28 의 장면 · 옵션 19개 = 실제 rsync', () {
|
||||||
|
late Directory tmp;
|
||||||
|
late String s, d;
|
||||||
|
setUp(() {
|
||||||
|
tmp = Directory.systemTemp.createTempSync('jj_rsfilter28_');
|
||||||
|
s = p.join(tmp.path, 's');
|
||||||
|
d = p.join(tmp.path, 'd');
|
||||||
|
for (final r in ['a.txt', 'b.log', 'build/x.o', 'build/sub/y.o', 'sub/c.txt', 'sub/deep/d.txt', 'sub/deep/e.log',
|
||||||
|
'node_modules/m/i.js', 'src/node_modules/n.js', 'docs/readme.md', 'docs/img/p.png', '한글 폴더/가.txt', 'space dir/s.bak',
|
||||||
|
'Keep.TXT', 'tmp/t.txt', 'x/tmp/t2.txt', 'foo/bar', 'foo/q/bar']) {
|
||||||
|
File(p.join(s, r))
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('s:$r');
|
||||||
|
}
|
||||||
|
for (final r in ['extra.txt', 'keep.log', 'build/old.o', 'old/keep.log', 'old/gone.txt', 'sub/deep/z.txt', 'tmp/old.txt',
|
||||||
|
'onlydst/a.bak']) {
|
||||||
|
File(p.join(d, r))
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('d:$r');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tearDown(() => tmp.deleteSync(recursive: true));
|
||||||
|
final rsync = p.join(Directory.current.path, 'third_party', 'rsync', 'windows', 'rsync.exe');
|
||||||
|
|
||||||
|
for (final (i, opts) in [
|
||||||
|
'-a --delete --exclude=*.log --exclude=build/ --exclude=/sub/deep/',
|
||||||
|
'-a --delete --exclude=*.log',
|
||||||
|
'-a --delete --include=*/ --include=*.txt --exclude=*',
|
||||||
|
'-a --delete --exclude=node_modules/',
|
||||||
|
'-a --delete --exclude=/tmp/',
|
||||||
|
'-a --delete --exclude=tmp/',
|
||||||
|
'-a --delete --exclude=sub/deep',
|
||||||
|
'-a --delete --exclude=docs/***',
|
||||||
|
'-a --delete --exclude=foo/**/bar',
|
||||||
|
'-a --delete --exclude=**/bar',
|
||||||
|
'-a --delete --exclude=*.[lb][oa][gk]',
|
||||||
|
'-a --delete --exclude="space dir/"',
|
||||||
|
'-a --delete --exclude=한글*/',
|
||||||
|
'-a --delete --exclude=*.bak --delete-excluded',
|
||||||
|
'-a --delete --exclude=*.log --delete-excluded',
|
||||||
|
'-a --delete --filter="- *.o" --filter="+ build/"',
|
||||||
|
'-a --delete --include=sub/deep/d.txt --exclude=sub/deep/*',
|
||||||
|
'-a --delete --exclude=?.txt',
|
||||||
|
'-a --delete --exclude=*.TXT',
|
||||||
|
].indexed) {
|
||||||
|
test('${i + 1}: $opts', () async {
|
||||||
|
if (!Platform.isWindows || !File(rsync).existsSync()) return markTestSkipped('앱에 든 rsync 가 없음');
|
||||||
|
final r = await Process.run(rsync, ['-8', '-n', '-i', ...splitOptions(opts), '${toCygwinPath(s)}/', '${toCygwinPath(d)}/'],
|
||||||
|
stdoutEncoding: utf8, stderrEncoding: utf8);
|
||||||
|
expect(r.exitCode, 0, reason: '${r.stderr}');
|
||||||
|
final want = <String, String>{};
|
||||||
|
for (final l in (r.stdout as String).split('\n')) {
|
||||||
|
final x = l.trimRight();
|
||||||
|
if (x.length < 13) continue;
|
||||||
|
final code = x.substring(0, 11), path = x.substring(12).replaceAll(RegExp(r'/$'), '');
|
||||||
|
if (path == '.' || path.isEmpty) continue;
|
||||||
|
if (code.startsWith('*deleting')) {
|
||||||
|
want[path] = 'delete';
|
||||||
|
} else if (code.contains('+++++')) {
|
||||||
|
want[path] = 'add';
|
||||||
|
} else if (code[0] == '>' || code[0] == '<') {
|
||||||
|
want[path] = 'update';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -n 이 적는 "*deleting 폴더/" 중 실제로는 못 지우는 것 (안에 거른 것이 남음) 은 빼고, 지워지는 폴더 안의 것은 폴더 하나로
|
||||||
|
for (final l in '${r.stdout}\n${r.stderr}'.split('\n')) {
|
||||||
|
final m = RegExp(r'cannot delete non-empty directory: (.+)$').firstMatch(l.trimRight());
|
||||||
|
if (m != null) want.remove(m[1]);
|
||||||
|
}
|
||||||
|
final gone = [for (final e in want.entries) if (e.value == 'delete') e.key];
|
||||||
|
want.removeWhere((k, v) => v == 'delete' && gone.any((g) => k.startsWith('$g/')));
|
||||||
|
final items = await previewSync(s, d, toRight: true, delete: optionsDelete(opts), filter: RsyncFilter.parse(opts));
|
||||||
|
final got = {
|
||||||
|
for (final x in items)
|
||||||
|
if (x.action == PreviewAction.add || x.action == PreviewAction.update || x.action == PreviewAction.delete)
|
||||||
|
x.rel: x.action == PreviewAction.add ? 'add' : x.action == PreviewAction.update ? 'update' : 'delete',
|
||||||
|
};
|
||||||
|
expect(got, want);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue