test: drive utils coverage to 100%#955
Conversation
- add focused coverage tests for utils, csv, and walker - add minimal utils seams and tightly coupled bug fixes for hard-to-hit paths - validate 100 percent coverage for the root utils package and rerun make test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Yay, your first pull request! 👍 A contributor will be by to give feedback soon. In the meantime, you can find updates in the #github-notifications channel in the community Slack. |
simihablo
left a comment
There was a problem hiding this comment.
Additional tests. :thu
|
Thanks for your contribution to Meshery! 🎉
|
There was a problem hiding this comment.
Code Review
This pull request significantly refactors various utility functions to enhance testability and error handling, primarily by introducing mockable interfaces and internal variables for file I/O, CUE operations, and external services. It also adds comprehensive unit tests for CSV parsing, error constructors, and general utilities. Critical feedback was provided regarding a Zip Slip vulnerability and potential file descriptor leaks in the unarchiving logic, along with a resource leak in the file writing utility where a file handle remains open upon a write error.
|
|
||
| if file.FileInfo().IsDir() { | ||
| err := os.Mkdir(file.Name, file.Mode()) | ||
| err := os.MkdirAll(filePath, file.Mode()) |
There was a problem hiding this comment.
This code is vulnerable to a "Zip Slip" attack. The filePath is constructed by joining the destination path with the file name from the archive without validating that the resulting path remains within the destination directory. A malicious archive could contain file names with path traversal elements (e.g., ../../etc/passwd) to overwrite sensitive files on the system. You should validate that the cleaned filePath has the destination path as a prefix.
| defer func() { | ||
| _ = fd.Close() | ||
| }() |
There was a problem hiding this comment.
Using defer inside a loop can lead to resource exhaustion, specifically file descriptor leaks. Deferred functions are only executed when the surrounding function returns, not at the end of each loop iteration. Since this loop processes all files in a ZIP archive, it could exceed the process's file descriptor limit if the archive contains many files. This also applies to the openedFile.Close() defer on line 110. Consider wrapping the loop body in a function literal or closing the files explicitly.
| // WriteToFile writes the given content to the given file path | ||
| func WriteToFile(path string, content string) error { | ||
| file, err := os.Create(path) | ||
| file, err := createWritableFile(path) |
There was a problem hiding this comment.
There was a problem hiding this comment.
Pull request overview
This PR focuses on driving statement coverage of the utils package (and related utils/csv and utils/walker) to 100% by adding targeted tests and introducing small package-private seams to make hard-to-reach error paths testable.
Changes:
- Added extensive coverage tests for
utils,utils/csv, andutils/walker. - Introduced package-private seams (function variables/interfaces) in
utilsto allow deterministic testing of error paths (archive, CUE, file IO, Google Sheets, SVG). - Made a few behavior tweaks to improve robustness and enable coverage (e.g., skipping blank CSV rows in
Git(), saferErrUnmarshalInvalidformatting).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/walker/walker_test.go | Adds coverage tests for local directory walking and git/GitHub walkers. |
| utils/utils.go | Adds IO/marshal/tar seams and improves YAML write panic handling; updates some callsites to use seams. |
| utils/utils_coverage_test.go | Adds focused “coverage-driving” tests across many utils helpers and error branches. |
| utils/unarchive.go | Adds seams for zip/tar extraction and adjusts zip extraction behavior. |
| utils/svg_utils.go | Adds seam for XML token encoding to test failure paths. |
| utils/google.go | Adds seam for Sheets service construction to test error handling. |
| utils/git.go | Adjusts parsing to skip blank rows when reading git version data. |
| utils/error.go | Avoids panic when reflect.Type is nil in ErrUnmarshalInvalid. |
| utils/error_constructors_test.go | Adds tests asserting error constructors expose expected error codes. |
| utils/cue.go | Adds seams for formatting/compiling/lookup to test error paths deterministically. |
| utils/csv/csv_test.go | Adds coverage tests for CSV parser read/parse and error wrapping behavior. |
Comments suppressed due to low confidence (1)
utils/utils.go:339
- If
WriteStringfails,WriteToFilereturns without closing the newly created file, leaking a file descriptor. Ensure the file is closed on all paths (e.g., deferClose()immediately after creation, or explicitly close before returning the write error).
file, err := createWritableFile(path)
if err != nil {
return ErrCreateFile(err, path)
}
_, err = file.WriteString(content)
if err != nil {
return ErrWriteFile(err, path)
}
// Close the file to save the changes.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| filePath := filepath.Join(path, file.Name) | ||
|
|
||
| if file.FileInfo().IsDir() { | ||
| err := os.Mkdir(file.Name, file.Mode()) | ||
| err := os.MkdirAll(filePath, file.Mode()) | ||
| if err != nil { |
There was a problem hiding this comment.
ExtractZip is vulnerable to ZipSlip/path traversal: file.Name can contain absolute paths or .. segments, and filepath.Join(path, file.Name) can escape the intended destination directory. Sanitize and validate the resulting path stays within path before creating directories/files.
| for _, file := range zipReader.File { | ||
|
|
||
| fd, err := file.Open() | ||
| defer func() { | ||
| _ = fd.Close() | ||
| }() | ||
|
|
||
| fd, err := openZipEntry(file) | ||
| if err != nil { | ||
| return ErrExtractZip(err, path) | ||
| } | ||
| defer func() { | ||
| _ = fd.Close() | ||
| }() |
There was a problem hiding this comment.
defer fd.Close() is executed inside the zip iteration loop, so all zip entry readers stay open until ExtractZip returns. On large archives this can exhaust file descriptors; close fd at the end of each iteration (and avoid defer inside the loop).
| openedFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) | ||
| if err != nil { | ||
| return ErrExtractZip(err, path) | ||
| } | ||
| defer func() { | ||
| _ = openedFile.Close() | ||
| }() | ||
| _, err = copyZipBuffer(openedFile, fd, buffer) | ||
| if err != nil { | ||
| return ErrExtractZip(err, path) | ||
| } |
There was a problem hiding this comment.
defer openedFile.Close() is inside the zip iteration loop; if many files are extracted, the destination file descriptors remain open until function exit. Close each file explicitly after copyZipBuffer (or scope the defer to a helper func) to avoid FD exhaustion.
| defer func() { _ = encoder.Close() }() | ||
| defer func() { | ||
| if recovered := recover(); recovered != nil { | ||
| switch value := recovered.(type) { | ||
| case error: | ||
| err = ErrMarshal(value) | ||
| default: | ||
| err = ErrMarshal(fmt.Errorf("%v", value)) | ||
| } | ||
| } | ||
| }() |
There was a problem hiding this comment.
The added recover() in WriteYamlToFile will convert all panics (including runtime.Error like nil-pointer dereferences) into ErrMarshal, which can mask serious bugs and make failures harder to diagnose. Consider only recovering expected marshal panics (or re-panicking for runtime.Error) so genuine programming errors still crash loudly.
| if err != nil { | ||
| return ErrExtractTarXZ(err, path) | ||
| } | ||
| if _, err = io.Copy(outFile, tarReader); err != nil { | ||
| if _, err = copyTarContent(outFile, tarReader); err != nil { | ||
| return ErrExtractTarXZ(err, path) | ||
| } | ||
| _ = outFile.Close() |
There was a problem hiding this comment.
ExtractTarGz still builds output paths from header.Name (via filepath.Join(path, header.Name) in this loop) without validating for absolute paths or .. segments, which enables TarSlip/path traversal writes outside path. Add path sanitization/validation before MkdirAll/Create so extracted files stay within the destination directory.
Summary
utils,utils/csv, andutils/walkerutilsutilspackage at100.0%statement coverageTesting
go test ./utils ./utils/csv ./utils/walkergo test -coverprofile=/tmp/utils.cover ./utilsmake test