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

Commit 5b2e0d0

Browse files
committed
refactor requested changes, minor improvements
- `project` - `checkCfgFilenames` - Improve function and code comments - Use boolean value indicating whether value was found in actual filenames. If manifest file was not found, return `errProjectNotFound`. Use boolean to determine if lock file was not found instead of length check. - `TestCheckCfgFilenames` - Add code comments for test cases explaining the expected behavior - `fs` - `ReadActualFilenames` - Use cleaner check(`<=` ➡ `==`) to see if `names` parameter for `ReadActualFilenames` actually has any values. - Use `Readdirnames` instead of `Readdir`. This is expected to perform better in most cases. - `TestReadActualFilenames` - Add code comments for test cases explaining the expected behavior - general - Make line length for code comments consistent(90), add periods where appropriate. - String formatting, use %q instead of '%s'
1 parent 5bfb958 commit 5b2e0d0

File tree

5 files changed

+82
-47
lines changed

5 files changed

+82
-47
lines changed

context_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,8 @@ func TestLoadProjectCfgFileCase(t *testing.T) {
269269
t.Skip("skip this test on non-Windows, non-macOS")
270270
}
271271

272-
// Here we test that a manifest filename with incorrect case
273-
// throws an error. Similar error will also be thrown for the
274-
// lock file as well which has been tested in
272+
// Here we test that a manifest filename with incorrect case throws an error. Similar
273+
// error will also be thrown for the lock file as well which has been tested in
275274
// `project_test.go#TestCheckCfgFilenames`. So not repeating here.
276275

277276
h := test.NewHelper(t)
@@ -299,7 +298,7 @@ func TestLoadProjectCfgFileCase(t *testing.T) {
299298
}
300299

301300
expectedErrMsg := fmt.Sprintf(
302-
"manifest filename '%s' does not match '%s'",
301+
"manifest filename %q does not match %q",
303302
invalidMfName, ManifestName,
304303
)
305304

internal/fs/fs.go

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,13 @@ var errPathNotDir = errors.New("given path is not a directory")
271271
// On case sensitive file systems like ext4, it will check if those files exist using
272272
// `os.Stat` and return a map with key and value as filenames which exist in the folder.
273273
//
274-
// Otherwise, it reads the contents of the directory
274+
// Otherwise, it reads the contents of the directory and returns a map which has the
275+
// given file name as the key and actual filename as the value if it was found.
275276
func ReadActualFilenames(dirPath string, names []string) (map[string]string, error) {
276277
actualFilenames := make(map[string]string, len(names))
277-
if len(names) <= 0 {
278-
// This isn't expected to happen for current usage.
279-
// Adding edge case handling, maybe useful in future
278+
if len(names) == 0 {
279+
// This isn't expected to happen for current usage. Adding edge case handling,
280+
// as it may be useful in future.
280281
return actualFilenames, nil
281282
}
282283
// First, check that the given path is valid and it is a directory
@@ -289,23 +290,23 @@ func ReadActualFilenames(dirPath string, names []string) (map[string]string, err
289290
return nil, errPathNotDir
290291
}
291292

292-
// Ideally, we would use `os.Stat` for getting the actual file names
293-
// but that returns the name we passed in as an argument and not the actual filename.
294-
// So we are forced to list the directory contents and check
295-
// against that. Since this check is costly, we do it only if absolutely necessary.
293+
// Ideally, we would use `os.Stat` for getting the actual file names but that returns
294+
// the name we passed in as an argument and not the actual filename. So we are forced
295+
// to list the directory contents and check against that. Since this check is costly,
296+
// we do it only if absolutely necessary.
296297
caseSensitive, err := IsCaseSensitiveFilesystem(dirPath)
297298
if err != nil {
298299
return nil, errors.Wrap(err, "failed to read actual filenames")
299300
}
300301
if caseSensitive {
301-
// There will be no difference between actual filename and given filename
302-
// So just check if those files exist.
302+
// There will be no difference between actual filename and given filename. So
303+
// just check if those files exist.
303304
for _, name := range names {
304305
_, err := os.Stat(filepath.Join(dirPath, name))
305306
if err == nil {
306307
actualFilenames[name] = name
307308
} else if !os.IsNotExist(err) {
308-
// Some unexpected err, return it.
309+
// Some unexpected err, wrap and return it.
309310
return nil, errors.Wrap(err, "failed to read actual filenames")
310311
}
311312
}
@@ -318,29 +319,27 @@ func ReadActualFilenames(dirPath string, names []string) (map[string]string, err
318319
}
319320
defer dir.Close()
320321

321-
// Pass -1 to read all files in directory
322-
files, err := dir.Readdir(-1)
322+
// Pass -1 to read all filenames in directory
323+
filenames, err := dir.Readdirnames(-1)
323324
if err != nil {
324325
return nil, errors.Wrap(err, "failed to read actual filenames")
325326
}
326327

327-
// namesMap holds the mapping from lowercase name to search name.
328-
// Using this, we can avoid repeatedly looping through names.
328+
// namesMap holds the mapping from lowercase name to search name. Using this, we can
329+
// avoid repeatedly looping through names.
329330
namesMap := make(map[string]string, len(names))
330331
for _, name := range names {
331332
namesMap[strings.ToLower(name)] = name
332333
}
333334

334-
for _, file := range files {
335-
if file.Mode().IsRegular() {
336-
searchName, ok := namesMap[strings.ToLower(file.Name())]
337-
if ok {
338-
// We are interested in this file, case insensitive match successful
339-
actualFilenames[searchName] = file.Name()
340-
if len(actualFilenames) == len(names) {
341-
// We found all that we were looking for
342-
return actualFilenames, nil
343-
}
335+
for _, filename := range filenames {
336+
searchName, ok := namesMap[strings.ToLower(filename)]
337+
if ok {
338+
// We are interested in this file, case insensitive match successful.
339+
actualFilenames[searchName] = filename
340+
if len(actualFilenames) == len(names) {
341+
// We found all that we were looking for.
342+
return actualFilenames, nil
344343
}
345344
}
346345
}

internal/fs/fs_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,12 @@ func TestReadActualFilenames(t *testing.T) {
275275
h.TempDir("")
276276
tmpPath := h.Path(".")
277277

278+
// First, check the scenarios for which we expect an error.
278279
_, err := ReadActualFilenames(filepath.Join(tmpPath, "does_not_exists"), []string{""})
279280
switch {
280281
case err == nil:
281282
t.Fatal("expected err for non-existing folder")
283+
// use `errors.Cause` because the error is wrapped and returned
282284
case !os.IsNotExist(errors.Cause(err)):
283285
t.Fatalf("unexpected error: %+v", err)
284286
}
@@ -296,11 +298,23 @@ func TestReadActualFilenames(t *testing.T) {
296298
names []string
297299
want map[string]string
298300
}{
299-
{nil, nil, map[string]string{}}, {
301+
// If we supply no filenames to the function, it should return an empty map.
302+
{nil, nil, map[string]string{}},
303+
// If the directory contains the given file with different case, it should return
304+
// a map which has the given filename as the key and actual filename as the value.
305+
{
300306
[]string{"test1.txt"},
301307
[]string{"Test1.txt"},
302308
map[string]string{"Test1.txt": "test1.txt"},
303-
}, {
309+
},
310+
// 1. If the given filename is same as the actual filename, map should have the
311+
// same key and value for the file.
312+
// 2. If the given filename is present with different case for file extension,
313+
// it should return a map which has the given filename as the key and actual
314+
// filename as the value.
315+
// 3. If the given filename is not present even with a different case, the map
316+
// returned should not have an entry for that filename.
317+
{
304318
[]string{"test2.txt", "test3.TXT"},
305319
[]string{"test2.txt", "Test3.txt", "Test4.txt"},
306320
map[string]string{

project.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,16 @@ func findProjectRoot(from string) (string, error) {
4343
}
4444

4545
// checkCfgFilenames validates filename case for the manifest and lock files.
46-
// This is relevant on case-insensitive systems like Windows.
46+
//
47+
// This is relevant on case-insensitive file systems like Windows and macOS.
48+
//
49+
// If manifest file is not found, it returns an error indicating the project could not be
50+
// found. If it is found but the case does not match, an error is returned. If a lock
51+
// file is not found, no error is returned as lock file is optional. If it is found but
52+
// the case does not match, an error is returned.
4753
func checkCfgFilenames(projectRoot string) error {
48-
// ReadActualFilenames is actually costly. Since this check is not relevant
49-
// to case-sensitive filesystems like ext4, try for an early return.
54+
// ReadActualFilenames is actually costly. Since this check is not relevant to
55+
// case-sensitive filesystems like ext4(linux), try for an early return.
5056
caseSensitive, err := fs.IsCaseSensitiveFilesystem(projectRoot)
5157
if err != nil {
5258
return errors.Wrap(err, "could not check validity of configuration filenames")
@@ -61,20 +67,22 @@ func checkCfgFilenames(projectRoot string) error {
6167
return errors.Wrap(err, "could not check validity of configuration filenames")
6268
}
6369

64-
// Since this check is done after `findProjectRoot`, we can assume that
65-
// manifest file will be present. Even if it is not, error will still be thrown.
66-
// But error message will be a tad inaccurate.
67-
actualMfName := actualFilenames[ManifestName]
70+
actualMfName, found := actualFilenames[ManifestName]
71+
if !found {
72+
// Ideally this part of the code won't ever be executed if it is called after
73+
// `findProjectRoot`. But be thorough and handle it anyway.
74+
return errProjectNotFound
75+
}
6876
if actualMfName != ManifestName {
69-
return fmt.Errorf("manifest filename '%s' does not match '%s'", actualMfName, ManifestName)
77+
return fmt.Errorf("manifest filename %q does not match %q", actualMfName, ManifestName)
7078
}
7179

72-
// If a file is not found, the string map returned by `fs.ReadActualFilenames`
73-
// will not have an entry for the given filename. Since the lock file
74-
// is optional, we should check for equality only if it was found.
75-
actualLfName := actualFilenames[LockName]
76-
if len(actualLfName) > 0 && actualLfName != LockName {
77-
return fmt.Errorf("lock filename '%s' does not match '%s'", actualLfName, LockName)
80+
// If a file is not found, the string map returned by `fs.ReadActualFilenames` will
81+
// not have an entry for the given filename. Since the lock file is optional, we
82+
// should check for equality only if it was found.
83+
actualLfName, found := actualFilenames[LockName]
84+
if found && actualLfName != LockName {
85+
return fmt.Errorf("lock filename %q does not match %q", actualLfName, LockName)
7886
}
7987

8088
return nil

project_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func TestCheckCfgFilenames(t *testing.T) {
7070

7171
errMsgFor := func(filetype, filename string) func(string) string {
7272
return func(name string) string {
73-
return fmt.Sprintf("%s filename '%s' does not match '%s'", filetype, name, filename)
73+
return fmt.Sprintf("%s filename %q does not match %q", filetype, name, filename)
7474
}
7575
}
7676

@@ -85,29 +85,44 @@ func TestCheckCfgFilenames(t *testing.T) {
8585
createFiles []string
8686
wantErrMsg string
8787
}{
88+
// No error should be returned when the project contains a valid manifest file
89+
// but no lock file.
8890
{false, []string{ManifestName}, ""},
91+
// No error should be returned when the project contains a valid manifest file as
92+
// well as a valid lock file.
8993
{false, []string{ManifestName, LockName}, ""},
90-
{true, nil, manifestErrMsg("")},
94+
// Error indicating the project was not found should be returned if a manifest
95+
// file is not found.
96+
{true, nil, errProjectNotFound.Error()},
97+
// Error should be returned if the project has a manifest file with invalid name
98+
// but no lock file.
9199
{true, []string{invalidMfName}, manifestErrMsg(invalidMfName)},
100+
// Error should be returned if the project has a valid manifest file and an
101+
// invalid lock file.
92102
{true, []string{ManifestName, invalidLfName}, lockErrMsg(invalidLfName)},
93103
}
94104

95105
for _, c := range cases {
96106
h := test.NewHelper(t)
97107
defer h.Cleanup()
98108

109+
// Create a temporary directory which we will use as the project folder.
99110
h.TempDir("")
100111
tmpPath := h.Path(".")
101112

113+
// Create any files that are needed for the test before invoking
114+
// `checkCfgFilenames`.
102115
for _, file := range c.createFiles {
103116
h.TempFile(file, "")
104117
}
105118
err := checkCfgFilenames(tmpPath)
106119

107120
if c.wantErr {
108121
if err == nil {
122+
// We were expecting an error but did not get one.
109123
t.Fatalf("unexpected error message: \n\t(GOT) nil\n\t(WNT) %s", c.wantErrMsg)
110124
} else if err.Error() != c.wantErrMsg {
125+
// We got an error but it is not the one we were expecting.
111126
t.Fatalf("unexpected error message: \n\t(GOT) %s\n\t(WNT) %s", err.Error(), c.wantErrMsg)
112127
}
113128
} else if err != nil {

0 commit comments

Comments
 (0)