628 lines
19 KiB
Go
628 lines
19 KiB
Go
package tsctests
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"maps"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/incremental"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/execute/tsc"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/harnessutil"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/stringtestutil"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/iovfs"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest"
|
|
)
|
|
|
|
type FileMap map[string]any
|
|
|
|
var tscLibPath = "/home/src/tslibs/TS/Lib"
|
|
|
|
var tscDefaultLibContent = stringtestutil.Dedent(`
|
|
/// <reference no-default-lib="true"/>
|
|
interface Boolean {}
|
|
interface Function {}
|
|
interface CallableFunction {}
|
|
interface NewableFunction {}
|
|
interface IArguments {}
|
|
interface Number { toExponential: any; }
|
|
interface Object {}
|
|
interface RegExp {}
|
|
interface String { charAt: any; }
|
|
interface Array<T> { length: number; [n: number]: T; }
|
|
interface ReadonlyArray<T> {}
|
|
interface SymbolConstructor {
|
|
(desc?: string | number): symbol;
|
|
for(name: string): symbol;
|
|
readonly toStringTag: symbol;
|
|
}
|
|
declare var Symbol: SymbolConstructor;
|
|
interface Symbol {
|
|
readonly [Symbol.toStringTag]: string;
|
|
}
|
|
declare const console: { log(msg: any): void; };
|
|
`)
|
|
|
|
func getTestLibPathFor(libName string) string {
|
|
var libFile string
|
|
if value, ok := tsoptions.LibMap.Get(libName); ok {
|
|
libFile = value.(string)
|
|
} else {
|
|
libFile = "lib." + libName + ".d.ts"
|
|
}
|
|
return tscLibPath + "/" + libFile
|
|
}
|
|
|
|
type TestClock struct {
|
|
start time.Time
|
|
now time.Time
|
|
nowMu sync.Mutex
|
|
}
|
|
|
|
func (t *TestClock) Now() time.Time {
|
|
t.nowMu.Lock()
|
|
defer t.nowMu.Unlock()
|
|
if t.now.IsZero() {
|
|
t.now = t.start
|
|
}
|
|
t.now = t.now.Add(1 * time.Second) // Simulate some time passing
|
|
return t.now
|
|
}
|
|
|
|
func (t *TestClock) SinceStart() time.Duration {
|
|
return t.Now().Sub(t.start)
|
|
}
|
|
|
|
func NewTscSystem(files FileMap, useCaseSensitiveFileNames bool, cwd string) *testSys {
|
|
clock := &TestClock{start: time.Now()}
|
|
return &testSys{
|
|
fs: &testFs{
|
|
FS: vfstest.FromMapWithClock(files, useCaseSensitiveFileNames, clock),
|
|
},
|
|
cwd: cwd,
|
|
clock: clock,
|
|
}
|
|
}
|
|
|
|
func newTestSys(tscInput *tscInput, forIncrementalCorrectness bool) *testSys {
|
|
cwd := tscInput.cwd
|
|
if cwd == "" {
|
|
cwd = "/home/src/workspaces/project"
|
|
}
|
|
libPath := tscLibPath
|
|
if tscInput.windowsStyleRoot != "" {
|
|
libPath = tscInput.windowsStyleRoot + libPath[1:]
|
|
}
|
|
currentWrite := &strings.Builder{}
|
|
sys := NewTscSystem(tscInput.files, !tscInput.ignoreCase, cwd)
|
|
sys.defaultLibraryPath = libPath
|
|
sys.currentWrite = currentWrite
|
|
sys.tracer = harnessutil.NewTracerForBaselining(tspath.ComparePathsOptions{
|
|
UseCaseSensitiveFileNames: !tscInput.ignoreCase,
|
|
CurrentDirectory: cwd,
|
|
}, currentWrite)
|
|
sys.env = tscInput.env
|
|
sys.forIncrementalCorrectness = forIncrementalCorrectness
|
|
|
|
// Ensure the default library file is present
|
|
sys.ensureLibPathExists("lib.d.ts")
|
|
for _, libFile := range tsoptions.TargetToLibMap() {
|
|
sys.ensureLibPathExists(libFile)
|
|
}
|
|
for libFile := range tsoptions.LibFilesSet.Keys() {
|
|
sys.ensureLibPathExists(libFile)
|
|
}
|
|
return sys
|
|
}
|
|
|
|
type diffEntry struct {
|
|
content string
|
|
mTime time.Time
|
|
isWritten bool
|
|
symlinkTarget string
|
|
}
|
|
|
|
type snapshot struct {
|
|
snap map[string]*diffEntry
|
|
defaultLibs *collections.SyncSet[string]
|
|
}
|
|
|
|
type testSys struct {
|
|
currentWrite *strings.Builder
|
|
programBaselines strings.Builder
|
|
programIncludeBaselines strings.Builder
|
|
tracer *harnessutil.TracerForBaselining
|
|
serializedDiff *snapshot
|
|
forIncrementalCorrectness bool
|
|
|
|
fs *testFs
|
|
defaultLibraryPath string
|
|
cwd string
|
|
env map[string]string
|
|
clock *TestClock
|
|
}
|
|
|
|
var (
|
|
_ tsc.System = (*testSys)(nil)
|
|
_ tsc.CommandLineTesting = (*testSys)(nil)
|
|
)
|
|
|
|
func (s *testSys) Now() time.Time {
|
|
return s.clock.Now()
|
|
}
|
|
|
|
func (s *testSys) SinceStart() time.Duration {
|
|
return s.clock.SinceStart()
|
|
}
|
|
|
|
func (s *testSys) FS() vfs.FS {
|
|
return s.fs
|
|
}
|
|
|
|
func (s *testSys) fsFromFileMap() iovfs.FsWithSys {
|
|
return s.fs.FS.(iovfs.FsWithSys)
|
|
}
|
|
|
|
func (s *testSys) mapFs() *vfstest.MapFS {
|
|
return s.fsFromFileMap().FSys().(*vfstest.MapFS)
|
|
}
|
|
|
|
func (s *testSys) ensureLibPathExists(path string) {
|
|
path = s.defaultLibraryPath + "/" + path
|
|
if _, ok := s.fsFromFileMap().ReadFile(path); !ok {
|
|
if s.fs.defaultLibs == nil {
|
|
s.fs.defaultLibs = &collections.SyncSet[string]{}
|
|
}
|
|
s.fs.defaultLibs.Add(path)
|
|
err := s.fsFromFileMap().WriteFile(path, tscDefaultLibContent, false)
|
|
if err != nil {
|
|
panic("Failed to write default library file: " + err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testSys) DefaultLibraryPath() string {
|
|
return s.defaultLibraryPath
|
|
}
|
|
|
|
func (s *testSys) GetCurrentDirectory() string {
|
|
return s.cwd
|
|
}
|
|
|
|
func (s *testSys) Writer() io.Writer {
|
|
return s.currentWrite
|
|
}
|
|
|
|
func (s *testSys) WriteOutputIsTTY() bool {
|
|
return true
|
|
}
|
|
|
|
func (s *testSys) GetWidthOfTerminal() int {
|
|
if widthStr := s.GetEnvironmentVariable("TS_TEST_TERMINAL_WIDTH"); widthStr != "" {
|
|
return core.Must(strconv.Atoi(widthStr))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (s *testSys) GetEnvironmentVariable(name string) string {
|
|
return s.env[name]
|
|
}
|
|
|
|
func (s *testSys) OnEmittedFiles(result *compiler.EmitResult, mTimesCache *collections.SyncMap[tspath.Path, time.Time]) {
|
|
if result != nil {
|
|
for _, file := range result.EmittedFiles {
|
|
modTime := s.mapFs().GetModTime(file)
|
|
if s.serializedDiff != nil {
|
|
if diff, ok := s.serializedDiff.snap[file]; ok && diff.mTime.Equal(modTime) {
|
|
// Even though written, timestamp was reverted
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Ensure that the timestamp for emitted files is in the order
|
|
now := s.Now()
|
|
if err := s.fsFromFileMap().Chtimes(file, time.Time{}, now); err != nil {
|
|
panic("Failed to change time for emitted file: " + file + ": " + err.Error())
|
|
}
|
|
// Update the mTime cache in --b mode to store the updated timestamp so tests will behave deteministically when finding newest output
|
|
if mTimesCache != nil {
|
|
path := tspath.ToPath(file, s.GetCurrentDirectory(), s.FS().UseCaseSensitiveFileNames())
|
|
if _, found := mTimesCache.Load(path); found {
|
|
mTimesCache.Store(path, now)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testSys) OnListFilesStart(w io.Writer) {
|
|
fmt.Fprintln(w, listFileStart)
|
|
}
|
|
|
|
func (s *testSys) OnListFilesEnd(w io.Writer) {
|
|
fmt.Fprintln(w, listFileEnd)
|
|
}
|
|
|
|
func (s *testSys) OnStatisticsStart(w io.Writer) {
|
|
fmt.Fprintln(w, statisticsStart)
|
|
}
|
|
|
|
func (s *testSys) OnStatisticsEnd(w io.Writer) {
|
|
fmt.Fprintln(w, statisticsEnd)
|
|
}
|
|
|
|
func (s *testSys) OnBuildStatusReportStart(w io.Writer) {
|
|
fmt.Fprintln(w, buildStatusReportStart)
|
|
}
|
|
|
|
func (s *testSys) OnBuildStatusReportEnd(w io.Writer) {
|
|
fmt.Fprintln(w, buildStatusReportEnd)
|
|
}
|
|
|
|
func (s *testSys) OnWatchStatusReportStart() {
|
|
fmt.Fprintln(s.Writer(), watchStatusReportStart)
|
|
}
|
|
|
|
func (s *testSys) OnWatchStatusReportEnd() {
|
|
fmt.Fprintln(s.Writer(), watchStatusReportEnd)
|
|
}
|
|
|
|
func (s *testSys) GetTrace(w io.Writer) func(str string) {
|
|
return func(str string) {
|
|
fmt.Fprintln(w, traceStart)
|
|
defer fmt.Fprintln(w, traceEnd)
|
|
// With tsc -b building projects in parallel we cannot serialize the package.json lookup trace
|
|
// so trace as if it wasnt cached
|
|
s.tracer.TraceWithWriter(w, str, w == s.Writer())
|
|
}
|
|
}
|
|
|
|
func (s *testSys) writeHeaderToBaseline(builder *strings.Builder, program *incremental.Program) {
|
|
if builder.Len() != 0 {
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
if configFilePath := program.Options().ConfigFilePath; configFilePath != "" {
|
|
builder.WriteString(tspath.GetRelativePathFromDirectory(s.cwd, configFilePath, tspath.ComparePathsOptions{
|
|
UseCaseSensitiveFileNames: s.FS().UseCaseSensitiveFileNames(),
|
|
CurrentDirectory: s.GetCurrentDirectory(),
|
|
}) + "::\n")
|
|
}
|
|
}
|
|
|
|
func (s *testSys) OnProgram(program *incremental.Program) {
|
|
s.writeHeaderToBaseline(&s.programBaselines, program)
|
|
|
|
testingData := program.GetTestingData()
|
|
s.programBaselines.WriteString("SemanticDiagnostics::\n")
|
|
for _, file := range program.GetProgram().GetSourceFiles() {
|
|
if diagnostics, ok := testingData.SemanticDiagnosticsPerFile.Load(file.Path()); ok {
|
|
if oldDiagnostics, ok := testingData.OldProgramSemanticDiagnosticsPerFile.Load(file.Path()); !ok || oldDiagnostics != diagnostics {
|
|
s.programBaselines.WriteString("*refresh* " + file.FileName() + "\n")
|
|
}
|
|
} else {
|
|
s.programBaselines.WriteString("*not cached* " + file.FileName() + "\n")
|
|
}
|
|
}
|
|
|
|
// Write signature updates
|
|
s.programBaselines.WriteString("Signatures::\n")
|
|
for _, file := range program.GetProgram().GetSourceFiles() {
|
|
if kind, ok := testingData.UpdatedSignatureKinds[file.Path()]; ok {
|
|
switch kind {
|
|
case incremental.SignatureUpdateKindComputedDts:
|
|
s.programBaselines.WriteString("(computed .d.ts) " + file.FileName() + "\n")
|
|
case incremental.SignatureUpdateKindStoredAtEmit:
|
|
s.programBaselines.WriteString("(stored at emit) " + file.FileName() + "\n")
|
|
case incremental.SignatureUpdateKindUsedVersion:
|
|
s.programBaselines.WriteString("(used version) " + file.FileName() + "\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
var filesWithoutIncludeReason []string
|
|
var fileNotInProgramWithIncludeReason []string
|
|
includeReasons := program.GetProgram().GetIncludeReasons()
|
|
for _, file := range program.GetProgram().GetSourceFiles() {
|
|
if _, ok := includeReasons[file.Path()]; !ok {
|
|
filesWithoutIncludeReason = append(filesWithoutIncludeReason, string(file.Path()))
|
|
}
|
|
}
|
|
for path := range includeReasons {
|
|
if program.GetProgram().GetSourceFileByPath(path) == nil && !program.GetProgram().IsMissingPath(path) {
|
|
fileNotInProgramWithIncludeReason = append(fileNotInProgramWithIncludeReason, string(path))
|
|
}
|
|
}
|
|
if len(filesWithoutIncludeReason) > 0 || len(fileNotInProgramWithIncludeReason) > 0 {
|
|
s.writeHeaderToBaseline(&s.programIncludeBaselines, program)
|
|
s.programIncludeBaselines.WriteString("!!! Expected all files to have include reasons\nfilesWithoutIncludeReason::\n")
|
|
for _, file := range filesWithoutIncludeReason {
|
|
s.programIncludeBaselines.WriteString(" " + file + "\n")
|
|
}
|
|
s.programIncludeBaselines.WriteString("filesNotInProgramWithIncludeReason::\n")
|
|
for _, file := range fileNotInProgramWithIncludeReason {
|
|
s.programIncludeBaselines.WriteString(" " + file + "\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testSys) baselinePrograms(baseline *strings.Builder, header string) string {
|
|
baseline.WriteString(s.programBaselines.String())
|
|
s.programBaselines.Reset()
|
|
var result string
|
|
if s.programIncludeBaselines.Len() > 0 {
|
|
result += fmt.Sprintf("\n\n%s\n!!! Include reasons expectations don't match pls review!!!\n", header)
|
|
result += s.programIncludeBaselines.String()
|
|
s.programIncludeBaselines.Reset()
|
|
baseline.WriteString(result)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *testSys) serializeState(baseline *strings.Builder) {
|
|
s.baselineOutput(baseline)
|
|
s.baselineFSwithDiff(baseline)
|
|
// todo watch
|
|
// this.serializeWatches(baseline);
|
|
// this.timeoutCallbacks.serialize(baseline);
|
|
// this.immediateCallbacks.serialize(baseline);
|
|
// this.pendingInstalls.serialize(baseline);
|
|
// this.service?.baseline();
|
|
}
|
|
|
|
var (
|
|
fakeTimeStamp = "HH:MM:SS AM"
|
|
fakeDuration = "d.ddds"
|
|
|
|
buildStartingAt = "build starting at "
|
|
buildFinishedIn = "build finished in "
|
|
listFileStart = "!!! List files start"
|
|
listFileEnd = "!!! List files end"
|
|
statisticsStart = "!!! Statistics start"
|
|
statisticsEnd = "!!! Statistics end"
|
|
buildStatusReportStart = "!!! Build Status Report Start"
|
|
buildStatusReportEnd = "!!! Build Status Report End"
|
|
watchStatusReportStart = "!!! Watch Status Report Start"
|
|
watchStatusReportEnd = "!!! Watch Status Report End"
|
|
traceStart = "!!! Trace start"
|
|
traceEnd = "!!! Trace end"
|
|
)
|
|
|
|
func (s *testSys) baselineOutput(baseline io.Writer) {
|
|
fmt.Fprint(baseline, "\nOutput::\n")
|
|
output := s.getOutput(false)
|
|
fmt.Fprint(baseline, output)
|
|
}
|
|
|
|
type outputSanitizer struct {
|
|
forComparing bool
|
|
lines []string
|
|
index int
|
|
outputLines []string
|
|
}
|
|
|
|
func (o *outputSanitizer) addOutputLine(s string) {
|
|
if change := strings.ReplaceAll(s, fmt.Sprintf("'%s'", core.Version()), fmt.Sprintf("'%s'", harnessutil.FakeTSVersion)); change != s {
|
|
s = change
|
|
}
|
|
if change := strings.ReplaceAll(s, "Version "+core.Version(), "Version "+harnessutil.FakeTSVersion); change != s {
|
|
s = change
|
|
}
|
|
o.outputLines = append(o.outputLines, s)
|
|
}
|
|
|
|
func (o *outputSanitizer) sanitizeBuildStatusTimeStamp() string {
|
|
statusLine := o.lines[o.index]
|
|
hhSeparator := strings.IndexRune(statusLine, ':')
|
|
if hhSeparator < 2 {
|
|
panic("Expected timestamp")
|
|
}
|
|
return statusLine[:hhSeparator-2] + fakeTimeStamp + statusLine[hhSeparator+len(fakeTimeStamp)-2:]
|
|
}
|
|
|
|
func (o *outputSanitizer) transformLines() string {
|
|
for ; o.index < len(o.lines); o.index++ {
|
|
line := o.lines[o.index]
|
|
if strings.HasPrefix(line, buildStartingAt) {
|
|
if !o.forComparing {
|
|
o.addOutputLine(buildStartingAt + fakeTimeStamp)
|
|
}
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, buildFinishedIn) {
|
|
if !o.forComparing {
|
|
o.addOutputLine(buildFinishedIn + fakeDuration)
|
|
}
|
|
continue
|
|
}
|
|
if !o.addOrSkipLinesForComparing(listFileStart, listFileEnd, false, nil) &&
|
|
!o.addOrSkipLinesForComparing(statisticsStart, statisticsEnd, true, nil) &&
|
|
!o.addOrSkipLinesForComparing(traceStart, traceEnd, false, nil) &&
|
|
!o.addOrSkipLinesForComparing(buildStatusReportStart, buildStatusReportEnd, false, o.sanitizeBuildStatusTimeStamp) &&
|
|
!o.addOrSkipLinesForComparing(watchStatusReportStart, watchStatusReportEnd, false, o.sanitizeBuildStatusTimeStamp) {
|
|
o.addOutputLine(line)
|
|
}
|
|
}
|
|
return strings.Join(o.outputLines, "\n")
|
|
}
|
|
|
|
func (o *outputSanitizer) addOrSkipLinesForComparing(
|
|
lineStart string,
|
|
lineEnd string,
|
|
skipEvenIfNotComparing bool,
|
|
sanitizeFirstLine func() string,
|
|
) bool {
|
|
if o.lines[o.index] != lineStart {
|
|
return false
|
|
}
|
|
o.index++
|
|
isFirstLine := true
|
|
for ; o.index < len(o.lines); o.index++ {
|
|
if o.lines[o.index] == lineEnd {
|
|
return true
|
|
}
|
|
if !o.forComparing && !skipEvenIfNotComparing {
|
|
line := o.lines[o.index]
|
|
if isFirstLine && sanitizeFirstLine != nil {
|
|
line = sanitizeFirstLine()
|
|
isFirstLine = false
|
|
}
|
|
o.addOutputLine(line)
|
|
}
|
|
}
|
|
panic("Expected lineEnd" + lineEnd + " not found after " + lineStart)
|
|
}
|
|
|
|
func (s *testSys) getOutput(forComparing bool) string {
|
|
lines := strings.Split(s.currentWrite.String(), "\n")
|
|
transformer := &outputSanitizer{
|
|
forComparing: forComparing,
|
|
lines: lines,
|
|
outputLines: make([]string, 0, len(lines)),
|
|
}
|
|
return transformer.transformLines()
|
|
}
|
|
|
|
func (s *testSys) clearOutput() {
|
|
s.currentWrite.Reset()
|
|
s.tracer.Reset()
|
|
}
|
|
|
|
func (s *testSys) baselineFSwithDiff(baseline io.Writer) {
|
|
// todo: baselines the entire fs, possibly doesn't correctly diff all cases of emitted files, since emit isn't fully implemented and doesn't always emit the same way as strada
|
|
snap := map[string]*diffEntry{}
|
|
|
|
diffs := map[string]string{}
|
|
|
|
for path, file := range s.mapFs().Entries() {
|
|
if file.Mode&fs.ModeSymlink != 0 {
|
|
target, ok := s.mapFs().GetTargetOfSymlink(path)
|
|
if !ok {
|
|
panic("Failed to resolve symlink target: " + path)
|
|
}
|
|
newEntry := &diffEntry{symlinkTarget: target}
|
|
snap[path] = newEntry
|
|
s.addFsEntryDiff(diffs, newEntry, path)
|
|
continue
|
|
} else if file.Mode.IsRegular() {
|
|
newEntry := &diffEntry{content: string(file.Data), mTime: file.ModTime, isWritten: s.fs.writtenFiles.Has(path)}
|
|
snap[path] = newEntry
|
|
s.addFsEntryDiff(diffs, newEntry, path)
|
|
}
|
|
}
|
|
if s.serializedDiff != nil {
|
|
for path := range s.serializedDiff.snap {
|
|
if fileInfo := s.mapFs().GetFileInfo(path); fileInfo == nil {
|
|
// report deleted
|
|
s.addFsEntryDiff(diffs, nil, path)
|
|
}
|
|
}
|
|
}
|
|
var defaultLibs collections.SyncSet[string]
|
|
if s.fs.defaultLibs != nil {
|
|
s.fs.defaultLibs.Range(func(libPath string) bool {
|
|
defaultLibs.Add(libPath)
|
|
return true
|
|
})
|
|
}
|
|
s.serializedDiff = &snapshot{
|
|
snap: snap,
|
|
defaultLibs: &defaultLibs,
|
|
}
|
|
diffKeys := slices.Collect(maps.Keys(diffs))
|
|
slices.Sort(diffKeys)
|
|
for _, path := range diffKeys {
|
|
fmt.Fprint(baseline, "//// ["+path+"] ", diffs[path], "\n")
|
|
}
|
|
fmt.Fprintln(baseline)
|
|
s.fs.writtenFiles = collections.SyncSet[string]{} // Reset written files after baseline
|
|
}
|
|
|
|
func (s *testSys) addFsEntryDiff(diffs map[string]string, newDirContent *diffEntry, path string) {
|
|
var oldDirContent *diffEntry
|
|
var defaultLibs *collections.SyncSet[string]
|
|
if s.serializedDiff != nil {
|
|
oldDirContent = s.serializedDiff.snap[path]
|
|
defaultLibs = s.serializedDiff.defaultLibs
|
|
}
|
|
// todo handle more cases of fs changes
|
|
if oldDirContent == nil {
|
|
if s.fs.defaultLibs == nil || !s.fs.defaultLibs.Has(path) {
|
|
if newDirContent.symlinkTarget != "" {
|
|
diffs[path] = "-> " + newDirContent.symlinkTarget + " *new*"
|
|
} else {
|
|
diffs[path] = "*new* \n" + newDirContent.content
|
|
}
|
|
}
|
|
} else if newDirContent == nil {
|
|
diffs[path] = "*deleted*"
|
|
} else if newDirContent.content != oldDirContent.content {
|
|
diffs[path] = "*modified* \n" + newDirContent.content
|
|
} else if newDirContent.isWritten {
|
|
diffs[path] = "*rewrite with same content*"
|
|
} else if newDirContent.mTime != oldDirContent.mTime {
|
|
diffs[path] = "*mTime changed*"
|
|
} else if defaultLibs != nil && defaultLibs.Has(path) && s.fs.defaultLibs != nil && !s.fs.defaultLibs.Has(path) {
|
|
// Lib file that was read
|
|
diffs[path] = "*Lib*\n" + newDirContent.content
|
|
}
|
|
}
|
|
|
|
func (s *testSys) writeFileNoError(path string, content string, writeByteOrderMark bool) {
|
|
if err := s.fsFromFileMap().WriteFile(path, content, writeByteOrderMark); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *testSys) removeNoError(path string) {
|
|
if err := s.fsFromFileMap().Remove(path); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (s *testSys) readFileNoError(path string) string {
|
|
content, ok := s.fsFromFileMap().ReadFile(path)
|
|
if !ok {
|
|
panic("File not found: " + path)
|
|
}
|
|
return content
|
|
}
|
|
|
|
func (s *testSys) renameFileNoError(oldPath string, newPath string) {
|
|
s.writeFileNoError(newPath, s.readFileNoError(oldPath), false)
|
|
s.removeNoError(oldPath)
|
|
}
|
|
|
|
func (s *testSys) replaceFileText(path string, oldText string, newText string) {
|
|
content := s.readFileNoError(path)
|
|
content = strings.Replace(content, oldText, newText, 1)
|
|
s.writeFileNoError(path, content, false)
|
|
}
|
|
|
|
func (s *testSys) replaceFileTextAll(path string, oldText string, newText string) {
|
|
content := s.readFileNoError(path)
|
|
content = strings.ReplaceAll(content, oldText, newText)
|
|
s.writeFileNoError(path, content, false)
|
|
}
|
|
|
|
func (s *testSys) appendFile(path string, text string) {
|
|
content := s.readFileNoError(path)
|
|
s.writeFileNoError(path, content+text, false)
|
|
}
|
|
|
|
func (s *testSys) prependFile(path string, text string) {
|
|
content := s.readFileNoError(path)
|
|
s.writeFileNoError(path, text+content, false)
|
|
}
|