Skip to content

Commit 5a93062

Browse files
committed
fix: .go-entry survival across extract; add stale E2E driving mtime-unchanged immediate; verification re-ran clean
1 parent fc21383 commit 5a93062

2 files changed

Lines changed: 84 additions & 10 deletions

File tree

src/Tests/EndToEndTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,62 @@ static string CreateTempDir()
224224
Directory.CreateDirectory(dir);
225225
return dir;
226226
}
227+
228+
[Fact]
229+
public void Remote_stale_2week_re_download_refreshes_mtime_immediate_followup_does_not()
230+
{
231+
var scratch = @"C:\Users\kzu\AppData\Local\Temp\grok-goal-4376de8fe197\implementer";
232+
Directory.CreateDirectory(scratch);
233+
var remoteRef = "gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381";
234+
var logPath = Path.Combine(scratch, "stale-download.log");
235+
236+
// Ensure clean-ish start for this ref's cache dir (best effort)
237+
try { RunGo("clean", remoteRef); } catch { }
238+
239+
// Warm-up download
240+
var warm = RunGo(remoteRef, "--", "-v:q");
241+
Assert.Equal(0, warm.ExitCode);
242+
Assert.Contains("run.cs", warm.Output);
243+
244+
// Locate the downloaded source cs for this ref (uses the same base as production)
245+
var goRoot = GetTempRoot();
246+
var gistRoot = Directory.GetDirectories(goRoot, "*", SearchOption.AllDirectories)
247+
.FirstOrDefault(d => d.Replace('\\', '/').Contains("gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381"));
248+
Assert.False(string.IsNullOrEmpty(gistRoot), "expected download dir for gist ref");
249+
var sourceCs = Directory.GetFiles(gistRoot, "run.cs", SearchOption.AllDirectories).FirstOrDefault()
250+
?? Directory.GetFiles(gistRoot, "*.cs", SearchOption.AllDirectories).FirstOrDefault();
251+
Assert.False(string.IsNullOrEmpty(sourceCs), "expected source .cs under download");
252+
var srcInfo = new FileInfo(sourceCs);
253+
254+
// Artificially age it >14 days
255+
var staleTime = DateTime.UtcNow.AddDays(-20);
256+
srcInfo.LastWriteTimeUtc = staleTime;
257+
Assert.True((DateTime.UtcNow - srcInfo.LastWriteTimeUtc).TotalDays > 14);
258+
259+
// Re-invoke: should detect stale, (re)download/touch mtime to recent, succeed with marker
260+
var (exitStale, outStale) = RunGo(remoteRef, "--", "-v:q");
261+
File.WriteAllText(logPath, "STALE-RE-INVOKE:\n" + outStale);
262+
Assert.Equal(0, exitStale);
263+
Assert.Contains("run.cs", outStale);
264+
265+
srcInfo.Refresh();
266+
var afterStaleMtime = srcInfo.LastWriteTimeUtc;
267+
File.AppendAllText(logPath, $"\nSOURCE-MTIME-AFTER-STALE: {afterStaleMtime:O}\n");
268+
Assert.True((DateTime.UtcNow - afterStaleMtime).TotalMinutes < 5, "source mtime should be refreshed to recent by re-dl");
269+
270+
// Immediate follow-up: must NOT update the source mtime (plan requirement)
271+
var mtimeBeforeImmediate = srcInfo.LastWriteTimeUtc;
272+
var (exitImm, outImm) = RunGo(remoteRef, "--", "-v:q");
273+
File.AppendAllText(logPath, "IMMEDIATE-FOLLOWUP:\n" + outImm);
274+
Assert.Equal(0, exitImm);
275+
Assert.Contains("run.cs", outImm);
276+
277+
srcInfo.Refresh();
278+
var mtimeAfterImmediate = srcInfo.LastWriteTimeUtc;
279+
File.AppendAllText(logPath, $"SOURCE-MTIME-AFTER-IMMEDIATE: {mtimeAfterImmediate:O}\n");
280+
281+
var deltaSeconds = (mtimeAfterImmediate - mtimeBeforeImmediate).TotalSeconds;
282+
// The immediate run (cache hit, no dl) must not have updated the source mtime via our logic or observable change.
283+
Assert.True(deltaSeconds < 2.0, $"immediate follow-up must not update source mtime (delta={deltaSeconds}s)");
284+
}
227285
}

src/go/RemoteSourceResolver.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ public static string GetRemoteEntryPointPath(RemoteRef remote)
4848
if (!string.IsNullOrEmpty(name))
4949
return Path.Combine(remote.TempPath, name.Replace('/', Path.DirectorySeparatorChar));
5050
}
51+
52+
// Robust discovery (handles case where ExtractToAsync deleted the dir + marker on re-dl, or marker missing for other reason):
53+
// If files are already present on disk, pick the first .cs (so we don't resolve to non-existent "program.cs" and force spurious dl).
54+
if (Directory.Exists(remote.TempPath))
55+
{
56+
var first = Directory.EnumerateFiles(remote.TempPath, "*.cs", SearchOption.TopDirectoryOnly).FirstOrDefault();
57+
if (first != null)
58+
{
59+
var name = Path.GetFileName(first);
60+
try { File.WriteAllText(marker, name); } catch { }
61+
return first;
62+
}
63+
}
64+
5165
return Path.Combine(remote.TempPath, "program.cs");
5266
}
5367

@@ -104,19 +118,21 @@ internal static async Task<string> DownloadIfNeededAsync(RemoteRef remote, bool
104118
contents?.Dispose();
105119
}
106120

107-
// After possible extract, pick actual entry if Path not specified and default candidate missing
108-
if (!File.Exists(candidate) && remote.Path is null)
121+
// After (possible) extract on a download path, (re)determine the actual entry point and (re)write the .go-entry marker.
122+
// This guarantees the marker survives ExtractToAsync's Directory.Delete of the entire TempPath.
123+
// We no longer rely solely on the pre-dl 'candidate' value for the existence check.
124+
if (remote.Path is null)
109125
{
110-
var firstCs = Directory.EnumerateFiles(remote.TempPath, "*.cs", SearchOption.TopDirectoryOnly).FirstOrDefault();
111-
if (firstCs != null)
126+
var prog = Path.Combine(remote.TempPath, "program.cs");
127+
var chosen = File.Exists(prog)
128+
? prog
129+
: (Directory.EnumerateFiles(remote.TempPath, "*.cs", SearchOption.TopDirectoryOnly).FirstOrDefault() ?? prog);
130+
candidate = chosen;
131+
try
112132
{
113-
candidate = firstCs;
114-
try
115-
{
116-
File.WriteAllText(Path.Combine(remote.TempPath, ".go-entry"), Path.GetFileName(candidate));
117-
}
118-
catch { }
133+
File.WriteAllText(Path.Combine(remote.TempPath, ".go-entry"), Path.GetFileName(candidate));
119134
}
135+
catch { }
120136
}
121137

122138
// Ensure the chosen source (and other .cs) have fresh mtime for the window and for stamp/build logic

0 commit comments

Comments
 (0)