Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use source readers #76

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 53 additions & 13 deletions analyze.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package k6deps

import (
"bytes"
"encoding/json"
"errors"
"os"
)

Expand All @@ -10,12 +12,26 @@ import (
// Note: if archive is specified, the other three sources will not be taken into account,
// since the archive may contain them.
func Analyze(opts *Options) (Dependencies, error) {
if !opts.Archive.IsEmpty() {
if !opts.Archive.Ignore && !opts.Archive.IsEmpty() {
return archiveAnalizer(opts.Archive)()
}

if err := loadSources(opts); err != nil {
return nil, err
// if the manifest is not provided, we try to find it
// if not found, it will be empty and ignored by the analyzer
if !opts.Manifest.Ignore && opts.Manifest.IsEmpty() {
if err := opts.setManifest(); err != nil {
return nil, err
}
}

if !opts.Script.Ignore {
if err := loadScript(opts); err != nil {
return nil, err
}
}

if !opts.Env.Ignore {
loadEnv(opts)
}

return mergeAnalyzers(
Expand Down Expand Up @@ -44,16 +60,29 @@ func filterInvalid(from Dependencies) Dependencies {
}

func manifestAnalyzer(src Source) analyzer {
if len(src.Contents) == 0 {
if src.IsEmpty() {
return empty
}

return func() (Dependencies, error) {
reader, closer, err := src.contentReader()

// we tolerate the manifest file not existing
if errors.Is(err, os.ErrNotExist) { //nolint:forbidigo
return make(Dependencies), nil
}

if err != nil {
return nil, err
}
defer closer() //nolint:errcheck

var manifest struct {
Dependencies Dependencies `json:"dependencies,omitempty"`
}

if err := json.Unmarshal(src.Contents, &manifest); err != nil {
err = json.NewDecoder(reader).Decode(&manifest)
if err != nil {
return nil, err
}

Expand All @@ -62,14 +91,26 @@ func manifestAnalyzer(src Source) analyzer {
}

func scriptAnalyzer(src Source) analyzer {
if len(src.Contents) == 0 {
if src.IsEmpty() {
return empty
}

return func() (Dependencies, error) {
var deps Dependencies

if err := (&deps).UnmarshalJS(src.Contents); err != nil {
reader, closer, err := src.contentReader()
if err != nil {
return nil, err
}
defer closer() //nolint:errcheck

buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(reader)
if err != nil {
return nil, err
}

if err := (&deps).UnmarshalJS(buffer.Bytes()); err != nil {
return nil, err
}

Expand All @@ -94,6 +135,10 @@ func envAnalyzer(src Source) analyzer {
}

func archiveAnalizer(src Source) analyzer {
if src.IsEmpty() {
return empty
}

return func() (Dependencies, error) {
input := src.Reader
if input == nil {
Expand All @@ -106,12 +151,7 @@ func archiveAnalizer(src Source) analyzer {
input = tar
}

analyzer, err := processArchive(input)
if err != nil {
return nil, err
}

return analyzer()
return processArchive(input)
}
}

Expand Down
2 changes: 1 addition & 1 deletion analyze_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Test_scriptAnalyzer(t *testing.T) {
fn := scriptAnalyzer(src)
deps, err := fn()

require.NoError(t, err)
require.Error(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that right, that now we expect error? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I documented in the PR as a breaking change

If the script source is given a name but not content, the name is used as the script's file and its content is tried to be read. Before this was not the case because it was assumed the content was already read always

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, yes makes totally sense!

require.Empty(t, deps)

src.Contents = []byte(`"use k6 with @grafana/xk6-faker>v0.3.0";`)
Expand Down
45 changes: 26 additions & 19 deletions archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package k6deps

import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"io"
Expand All @@ -17,17 +16,16 @@ type archiveMetadata struct {

const maxFileSize = 1024 * 1024 * 10 // 10M

func processArchive(input io.Reader) (analyzer, error) {
func processArchive(input io.Reader) (Dependencies, error) {
reader := tar.NewReader(input)

analyzers := make([]analyzer, 0)

deps := Dependencies{}
for {
header, err := reader.Next()

switch {
case errors.Is(err, io.EOF):
return mergeAnalyzers(analyzers...), nil
return deps, nil
case err != nil:
return nil, err
case header == nil:
Expand All @@ -38,29 +36,37 @@ func processArchive(input io.Reader) (analyzer, error) {
continue
}

content := &bytes.Buffer{}
if _, err := io.CopyN(content, reader, maxFileSize); err != nil && !errors.Is(err, io.EOF) {
return nil, err
}

// if the file is metadata.json, we extract the dependencies from the env
if header.Name == "metadata.json" {
analyzer, err := analizeMetadata(content.Bytes())
d, err := analizeMetadata(reader)
if err != nil {
return nil, err
}

err = deps.Merge(d)
if err != nil {
return nil, err
}
analyzers = append(analyzers, analyzer)
continue
}

// analize the file content as an script
target := filepath.Clean(filepath.FromSlash(header.Name))
src := Source{
Name: target,
Contents: content.Bytes(),
Name: target,
Reader: io.LimitReader(reader, maxFileSize),
}

d, err := scriptAnalyzer(src)()
if err != nil {
return nil, err
}

analyzers = append(analyzers, scriptAnalyzer(src))
err = deps.Merge(d)
if err != nil {
return nil, err
}
continue
}
}

Expand All @@ -71,9 +77,9 @@ func shouldProcess(target string) bool {
}

// analizeMetadata extracts the dependencies from the metadata.json file
func analizeMetadata(content []byte) (analyzer, error) {
func analizeMetadata(input io.Reader) (Dependencies, error) {
metadata := archiveMetadata{}
if err := json.Unmarshal(content, &metadata); err != nil {
if err := json.NewDecoder(input).Decode(&metadata); err != nil {
return nil, err
}

Expand All @@ -82,8 +88,9 @@ func analizeMetadata(content []byte) (analyzer, error) {
Name: EnvDependencies,
Contents: []byte(value),
}
return envAnalyzer(src), nil

return envAnalyzer(src)()
}

return empty, nil
return Dependencies{}, nil
}
4 changes: 3 additions & 1 deletion cmd/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ Analyze the k6 test script and extract the extensions that the script depends on

### Sources

Dependencies can come from three sources: k6 test script, manifest file, `K6_DEPENDENCIES` environment variable. Instead of these three sources, a k6 archive can also be specified, which can contain all three sources (currently two actually, because the manifest file is not yet included in the k6 archive).
Dependencies can come from three sources: k6 test script, manifest file, `K6_DEPENDENCIES` environment variable. Instead of these three sources, a k6 archive can also be specified, which can contain all three sources (currently two actually, because the manifest file is not yet included in the k6 archive). An archive is a tar file, which can be created using the k6 archive command.

> *NOTE*: It is assumed that the script and all dependencies are in the archive. No external dependencies are analyzed.

The name of k6 test script or archive can be specified as the positional argument in the command invocation. Alternatively, the content can be provided in the stdin. If stdin is used, the input format ('js' for script of or 'tar' for archive) must be specified using the `--input` parameter.

Expand Down
Loading
Loading