Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 8a64a61

Browse files
committed
Treat ProjectRoot as case-insensitive in srcCoord
1 parent f2d1529 commit 8a64a61

File tree

2 files changed

+80
-46
lines changed

2 files changed

+80
-46
lines changed

internal/gps/manager_test.go

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -419,45 +419,55 @@ func TestFSCaseSensitivityConvergesSources(t *testing.T) {
419419
t.Skip("Skipping slow test in short mode")
420420
}
421421

422-
sm, clean := mkNaiveSM(t)
423-
defer clean()
422+
f := func(name string, pi1, pi2 ProjectIdentifier) {
423+
t.Run(name, func(t *testing.T) {
424+
t.Parallel()
425+
sm, clean := mkNaiveSM(t)
426+
defer clean()
424427

425-
pi1 := mkPI("github.com/sdboyer/deptest").normalize()
426-
sm.SyncSourceFor(pi1)
427-
sg1, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi1)
428-
if err != nil {
429-
t.Fatal(err)
430-
}
428+
sm.SyncSourceFor(pi1)
429+
sg1, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi1)
430+
if err != nil {
431+
t.Fatal(err)
432+
}
431433

432-
pi2 := mkPI("github.com/Sdboyer/deptest").normalize()
433-
sm.SyncSourceFor(pi2)
434-
sg2, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi2)
435-
if err != nil {
436-
t.Fatal(err)
437-
}
434+
sm.SyncSourceFor(pi2)
435+
sg2, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi2)
436+
if err != nil {
437+
t.Fatal(err)
438+
}
438439

439-
path1 := sg1.src.(*gitSource).repo.LocalPath()
440-
t.Log("path1:", path1)
441-
stat1, err := os.Stat(path1)
442-
if err != nil {
443-
t.Fatal(err)
444-
}
445-
path2 := sg2.src.(*gitSource).repo.LocalPath()
446-
t.Log("path2:", path2)
447-
stat2, err := os.Stat(path2)
448-
if err != nil {
449-
t.Fatal(err)
450-
}
440+
path1 := sg1.src.(*gitSource).repo.LocalPath()
441+
t.Log("path1:", path1)
442+
stat1, err := os.Stat(path1)
443+
if err != nil {
444+
t.Fatal(err)
445+
}
446+
path2 := sg2.src.(*gitSource).repo.LocalPath()
447+
t.Log("path2:", path2)
448+
stat2, err := os.Stat(path2)
449+
if err != nil {
450+
t.Fatal(err)
451+
}
451452

452-
same, count := os.SameFile(stat1, stat2), len(sm.srcCoord.srcs)
453-
if same && count != 1 {
454-
t.Log("are same, count", count)
455-
t.Fatal("on case-insensitive filesystem, case-varying sources should have been folded together but were not")
456-
}
457-
if !same && count != 2 {
458-
t.Log("not same, count", count)
459-
t.Fatal("on case-sensitive filesystem, case-varying sources should not have been folded together, but were")
453+
same, count := os.SameFile(stat1, stat2), len(sm.srcCoord.srcs)
454+
if same && count != 1 {
455+
t.Log("are same, count", count)
456+
t.Fatal("on case-insensitive filesystem, case-varying sources should have been folded together but were not")
457+
}
458+
if !same && count != 2 {
459+
t.Log("not same, count", count)
460+
t.Fatal("on case-sensitive filesystem, case-varying sources should not have been folded together, but were")
461+
}
462+
})
460463
}
464+
465+
folded := mkPI("github.com/sdboyer/deptest").normalize()
466+
casevar1 := mkPI("github.com/Sdboyer/deptest").normalize()
467+
casevar2 := mkPI("github.com/SdboyeR/deptest").normalize()
468+
f("folded first", folded, casevar1)
469+
f("folded second", casevar1, folded)
470+
f("both unfolded", casevar1, casevar2)
461471
}
462472

463473
// Regression test for #32

internal/gps/source.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,39 +91,58 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
9191
}
9292
sc.srcmut.RUnlock()
9393

94+
// Without a direct match, we must fold the input name to a generally
95+
// stable, caseless variant and primarily work from that. This ensures that
96+
// on case-insensitive filesystems, we do not end up with multiple
97+
// sourceGateways for paths that vary only by case. We perform folding
98+
// unconditionally, independent of whether the underlying fs is
99+
// case-sensitive, in order to ensure uniform behavior.
100+
//
101+
// This has significant implications. It is effectively deciding that the
102+
// ProjectRoot portion of import paths are case-insensitive, which is by no
103+
// means an invariant maintained by all hosting systems. If this presents a
104+
// problem in practice, then we can explore expanding the deduction system
105+
// to include case-sensitivity-for-roots metadata and treat it on a
106+
// host-by-host basis. Such cases would still be rejected by the Go
107+
// toolchain's compiler, though, and case-sensitivity in root names is
108+
// likely to be at least frowned on if not disallowed by most hosting
109+
// systems. So we follow this path, which is both a vastly simpler solution
110+
// and one that seems quite likely to work in practice.
111+
foldedNormalName := toFold(normalizedName)
112+
94113
// No gateway exists for this path yet; set up a proto, being careful to fold
95-
// together simultaneous attempts on the same path.
114+
// together simultaneous attempts on the same case-folded path.
96115
sc.psrcmut.Lock()
97-
if chans, has := sc.protoSrcs[normalizedName]; has {
116+
if chans, has := sc.protoSrcs[foldedNormalName]; has {
98117
// Another goroutine is already working on this normalizedName. Fold
99118
// in with that work by attaching our return channels to the list.
100119
rc := srcReturnChans{
101120
ret: make(chan *sourceGateway, 1),
102121
err: make(chan error, 1),
103122
}
104-
sc.protoSrcs[normalizedName] = append(chans, rc)
123+
sc.protoSrcs[foldedNormalName] = append(chans, rc)
105124
sc.psrcmut.Unlock()
106125
return rc.awaitReturn()
107126
}
108127

109-
sc.protoSrcs[normalizedName] = []srcReturnChans{}
128+
sc.protoSrcs[foldedNormalName] = []srcReturnChans{}
110129
sc.psrcmut.Unlock()
111130

112131
doReturn := func(sg *sourceGateway, err error) {
113132
sc.psrcmut.Lock()
114133
if sg != nil {
115-
for _, rc := range sc.protoSrcs[normalizedName] {
134+
for _, rc := range sc.protoSrcs[foldedNormalName] {
116135
rc.ret <- sg
117136
}
118137
} else if err != nil {
119-
for _, rc := range sc.protoSrcs[normalizedName] {
138+
for _, rc := range sc.protoSrcs[foldedNormalName] {
120139
rc.err <- err
121140
}
122141
} else {
123142
panic("sg and err both nil")
124143
}
125144

126-
delete(sc.protoSrcs, normalizedName)
145+
delete(sc.protoSrcs, foldedNormalName)
127146
sc.psrcmut.Unlock()
128147
}
129148

@@ -142,7 +161,7 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
142161
// and bailing out if we find an entry.
143162
var srcGate *sourceGateway
144163
sc.srcmut.RLock()
145-
if url, has := sc.nameToURL[normalizedName]; has {
164+
if url, has := sc.nameToURL[foldedNormalName]; has {
146165
if srcGate, has := sc.srcs[url]; has {
147166
sc.srcmut.RUnlock()
148167
doReturn(srcGate, nil)
@@ -155,10 +174,10 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
155174
srcGate = newSourceGateway(pd.mb, sc.supervisor, sc.cachedir)
156175

157176
// The normalized name is usually different from the source URL- e.g.
158-
// github.com/golang/dep/internal/gps vs. https://github.com/golang/dep/internal/gps. But it's
159-
// possible to arrive here with a full URL as the normalized name - and
160-
// both paths *must* lead to the same sourceGateway instance in order to
161-
// ensure disk access is correctly managed.
177+
// github.com/sdboyer/gps vs. https://github.com/sdboyer/gps. But it's
178+
// possible to arrive here with a full URL as the normalized name - and both
179+
// paths *must* lead to the same sourceGateway instance in order to ensure
180+
// disk access is correctly managed.
162181
//
163182
// Therefore, we now must query the sourceGateway to get the actual
164183
// sourceURL it's operating on, and ensure it's *also* registered at
@@ -176,6 +195,11 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
176195
defer sc.srcmut.Unlock()
177196
// Record the name -> URL mapping, even if it's a self-mapping.
178197
sc.nameToURL[normalizedName] = url
198+
// Make sure we have both the folded and unfolded names recorded in the map,
199+
// should they differ.
200+
if normalizedName != foldedNormalName {
201+
sc.nameToURL[foldedNormalName] = url
202+
}
179203

180204
if sa, has := sc.srcs[url]; has {
181205
// URL already had an entry in the main map; use that as the result.

0 commit comments

Comments
 (0)