builds dependency list successfully I think

master
Adam Veldhousen 4 years ago
parent 6b5ab437c4
commit 367880643b
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

5
.gitignore vendored

@ -1 +1,4 @@
.bin
.bin
node_modules
.vscode
package-lock.json

@ -1,28 +1,15 @@
package lib
import (
"archive/tar"
"encoding/json"
"fmt"
"io"
"net/http"
)
type PackageResponse struct {
Name string `json:"name"`
Versions map[string]Version `json:"versions"`
}
type Version struct {
VersionNumber string `json:"version"`
ID string `json:"_id"`
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
OptionalDependencies map[string]string `json:"optionalDependencies"`
Dist struct {
Sum string `json:"shasum"`
Tarball string `json:"tarball"`
}
Name string `json:"name"`
Version string `json:"version"`
Versions map[string]PackageRelease `json:"versions"`
}
func GetPackageInfo(name string) (PackageResponse, error) {
@ -48,16 +35,21 @@ func GetPackageInfo(name string) (PackageResponse, error) {
return pr, nil
}
func DownloadPackageTarball(sum, tarballURL string) (*tar.Reader, io.Closer, error) {
resp, err := http.Get(tarballURL)
if err != nil {
return nil, nil, fmt.Errorf("could not get tarball: %w", err)
}
type PackageRelease struct {
Name string `json:"name"`
VersionNumber string `json:"version"`
ID string `json:"_id"`
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
OptionalDependencies map[string]string `json:"optionalDependencies"`
Dist Distribution `json:"dist"`
}
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("Got a non 200 status code '%s': %v", resp.Status, resp.StatusCode)
}
func (v PackageRelease) TarURL() string {
return fmt.Sprintf("https://registry.npmjs.org/%s/-/%[1]s-%s.tgz", v.Name, v.VersionNumber)
}
reader := tar.NewReader(resp.Body)
return reader, resp.Body, nil
type Distribution struct {
Sum string `json:"shasum"`
Tarball string `json:"tarball"`
}

@ -0,0 +1,83 @@
package lib
import (
"reflect"
"testing"
)
func TestGetPackageInfo(t *testing.T) {
tests := []struct {
name string
inputName string
VersionCountExpected int
want PackageResponse
wantVersion string
wantErr bool
}{
{
name: "testcafe-browser-provider-puppeteer",
inputName: "testcafe-browser-provider-puppeteer",
VersionCountExpected: 8,
wantVersion: "1.0.1",
want: PackageResponse{
Name: "testcafe-browser-provider-puppeteer",
Versions: map[string]PackageRelease{
"1.0.1": PackageRelease{
Name: "testcafe-browser-provider-puppeteer",
VersionNumber: "1.0.1",
ID: "testcafe-browser-provider-puppeteer@1.0.1",
Dependencies: map[string]string{
"babel-runtime": "^6.11.6",
"puppeteer": "^0.10.2",
},
DevDependencies: map[string]string{
"babel-eslint": "^6.1.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-runtime": "^6.12.0",
"babel-preset-es2015": "^6.13.2",
"babel-preset-es2015-loose": "^7.0.0",
"babel-preset-stage-3": "^6.11.0",
"del": "^2.0.0",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
"gulp-eslint": "^3.0.1",
"gulp-mocha": "^3.0.1",
"node-version": "^1.0.0",
"publish-please": "^2.1.4",
},
Dist: Distribution{
Sum: "5bd7be2a401aec28fe9bef0b9aa9886bd56fd811",
Tarball: "https://registry.npmjs.org/testcafe-browser-provider-puppeteer/-/testcafe-browser-provider-puppeteer-1.0.1.tgz",
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetPackageInfo(tt.inputName)
if (err != nil) != tt.wantErr {
t.Errorf("GetPackageInfo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got.Versions) != tt.VersionCountExpected {
t.Errorf("got %d, expected %d", len(got.Versions), tt.VersionCountExpected)
}
wantVersion := tt.want.Versions[tt.wantVersion]
gotVersion, gotOk := got.Versions[tt.wantVersion]
if !gotOk {
t.Errorf("could not find version in response: %s", tt.wantVersion)
}
if !reflect.DeepEqual(gotVersion, wantVersion) {
t.Errorf("-----\ngot: \t%+v\nwant:\t%+v", gotVersion, wantVersion)
}
})
}
}

Binary file not shown.

@ -1,120 +1,102 @@
package lib
import (
"errors"
"bytes"
"fmt"
"strings"
"github.com/Masterminds/semver"
)
type DependencyNode struct {
Version
Name string
ResolvedDependencies []*DependencyNode
ResolvedDevDependencies []*DependencyNode
Error error
}
func BuildDependencyTree(rp RawPackage) (*DependencyNode, error) {
resolved := map[string]*DependencyNode{}
root := &DependencyNode{
Name: rp.Name,
ResolvedDependencies: populateDepList(rp.Dependencies, resolved),
ResolvedDevDependencies: populateDepList(rp.DevDependencies, resolved),
func BuildDependencyTree(rp PackageJSON) (*DependencyList, error) {
resolved := map[string]Dependency{}
unresolved := []Dependency{
Dependency{Name: rp.Name, Requires: rp.Dependencies},
}
current := root
for _, dep := range current.ResolvedDependencies {
dep.ResolvedDependencies = populateDepList(dep.Dependencies, resolved)
current = dep
}
for len(unresolved) > 0 {
unresolvedDep := unresolved[0]
unresolved = unresolved[1:len(unresolved)]
return root, nil
}
func populateDepList(dependencies map[string]string, resolved map[string]*DependencyNode) []*DependencyNode {
depList := make([]*DependencyNode, 0, len(dependencies))
for name, versionConstraint := range dependencies {
var node *DependencyNode
var err error
if resolvedDep, ok := resolved[name]; ok {
c, err := semver.NewConstraint(versionConstraint)
for k, p := range unresolvedDep.Requires {
meta, err := GetPackageInfo(k)
if err != nil {
node = &DependencyNode{
Name: name,
Error: fmt.Errorf("could not create a constraint: %w", err),
}
return nil, err
}
v, err := semver.NewVersion(resolvedDep.VersionNumber)
c, err := semver.NewConstraint(p)
if err != nil {
node = &DependencyNode{
Name: name,
Error: fmt.Errorf("could not detect version for a dist: %w", err),
}
return nil, err
}
valid, errs := c.Validate(v)
if len(errs) > 0 {
node = &DependencyNode{
Name: name,
Error: fmt.Errorf("could not parse version '%s': %+v", resolvedDep.VersionNumber, errs),
var selectedRelease PackageRelease
var max *semver.Version
for r, release := range meta.Versions {
v, err := semver.NewVersion(r)
if err != nil {
return nil, err
}
if c.Check(v) && (max == nil || v.GreaterThan(max)) {
selectedRelease = release
max = v
}
} else if valid {
node = resolvedDep
}
} else if strings.HasPrefix(versionConstraint, "github:") {
// skip github packages for now
node = &DependencyNode{
Name: name,
Error: errors.New("github package sources not supported."),
}
} else {
node, err = createNpmDependencyNode(name, versionConstraint)
if err != nil {
node = &DependencyNode{Name: name, Error: err}
} else {
resolved[node.Name] = node
}
unresolved = append(unresolved, Dependency{
Name: selectedRelease.Name,
Version: selectedRelease.VersionNumber,
Integrity: selectedRelease.Dist.Sum,
Resolved: selectedRelease.Dist.Tarball,
Requires: selectedRelease.Dependencies,
})
}
depList = append(depList, node)
// don't add root dep to the resolve list
if unresolvedDep.Name == rp.Name {
continue
}
resolved[unresolvedDep.Name] = unresolvedDep
}
return depList
return &DependencyList{
Name: rp.Name,
Version: rp.Version,
LockfileVersion: 1,
Requires: true,
Dependencies: resolved,
}, nil
}
func createNpmDependencyNode(name, versionConstraint string) (*DependencyNode, error) {
type Dependency struct {
Name string
Version string
Integrity string
Resolved string
Requires RequireList
}
pinfo, err := GetPackageInfo(name)
if err != nil {
return nil, fmt.Errorf("could not get package information for '%s': %w", name, err)
}
func (d Dependency) TarURL() string {
return fmt.Sprintf("https://registry.npmjs.org/%s/-/%[1]s-%s.tgz", d.Name, d.Version)
}
c, err := semver.NewConstraint(versionConstraint)
if err != nil {
return nil, fmt.Errorf("could not create a constraint: %w", err)
}
func (d Dependency) String() string {
header := fmt.Sprintf("%s@%s - %s\n", d.Name, d.Version, d.Integrity)
buf := bytes.NewBufferString(header)
var max *semver.Version
var selectedDist Version
for version, dist := range pinfo.Versions {
v, err := semver.NewVersion(version)
if err != nil {
return nil, fmt.Errorf("could not detect version for a dist: %w", err)
}
for k, v := range d.Requires {
fmt.Fprintf(buf, "\t%s => %s\n", k, v)
}
valid, errs := c.Validate(v)
if len(errs) > 0 {
return nil, fmt.Errorf("could not parse version '%s': %w", version, err)
}
return buf.String()
}
if valid && v.GreaterThan(max) {
max = v
selectedDist = dist
}
}
type RequireList map[string]string
return &DependencyNode{Name: name, Version: selectedDist}, nil
type DependencyList struct {
Name string `json:"name"`
Version string `json:"version"`
LockfileVersion int `json:"lockfileVersion"`
Requires bool `json:"requires"`
Dependencies map[string]Dependency `json:"dependencies"`
}

@ -1,34 +1,69 @@
package lib
import (
"reflect"
"log"
"testing"
)
func Test_populateDepList(t *testing.T) {
type Input struct {
dependencies map[string]string
resolved map[string]*DependencyNode
}
const simplePackageJSON = `
{
"name": "test",
"version": "1.0.0",
"scripts": { "test": "echo testing", "build": "echo hello > world.txt" },
"keywords": [],
"license": "UNLICENSED",
"dependencies": { "globby": "1.0.0" },
"devDependencies": { "mocha": "*" }
}
`
func init() {
log.SetFlags(log.Lshortfile)
}
func TestBuildDependencyTree(t *testing.T) {
tests := []struct {
name string
args Input
want []*DependencyNode
name string
args PackageJSON
depCount int
want map[string]Dependency
wantErr bool
}{
{
name: "resolve one package",
args: Input{
dependencies: map[string]string{"lodash": "4.17.15"},
resolved: map[string]*DependencyNode{},
name: "globby",
depCount: 13,
wantErr: false,
args: PackageJSON{
Name: "globby",
Dependencies: map[string]string{
"array-differ": "^1.0.0",
"array-union": "^1.0.1",
"async": "^0.9.0",
"glob": "^4.0.2",
},
},
},
{
name: "webpack test",
depCount: 13,
wantErr: false,
args: PackageJSON{
Name: "webpack",
Dependencies: map[string]string{
"webpack": "*",
},
},
want: []*DependencyNode{
&DependencyNode{
Name: "lodash",
Error: nil,
Version: Version{
ID: "lodash-4.17.15",
VersionNumber: "4.17.15",
},
},
{
name: "react test",
depCount: 13,
wantErr: false,
args: PackageJSON{
Name: "website",
Dependencies: map[string]string{
"react": "16",
},
},
},
@ -36,32 +71,18 @@ func Test_populateDepList(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := populateDepList(tt.args.dependencies, tt.args.resolved)
if len(got) != len(tt.want) {
t.Errorf("len(populateDepList()) = (%v), want (%v)", len(got), len(tt.want))
got, err := BuildDependencyTree(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("BuildDependencyTree() error = %v, wantErr %v", err, tt.wantErr)
return
}
for _, gotDep := range got {
found := false
for _, wantDep := range tt.want {
if gotDep.ID == wantDep.ID {
found = true
} else {
t.Logf("got: %+v\nwant:%+v", *gotDep, *wantDep)
continue
}
if !reflect.DeepEqual(*gotDep, *wantDep) {
t.Errorf("got %v, want %v", *gotDep, *wantDep)
break
}
}
t.Logf("%+v", got)
if !found {
t.Errorf("not found")
}
if len(got.Dependencies) != tt.depCount {
t.Errorf("BuildDependencyTree() = %v, want %v", len(got.Dependencies), tt.depCount)
return
}
})
}
}

@ -2,29 +2,29 @@ package lib
import (
"encoding/json"
"errors"
"fmt"
"os"
)
type RawPackage struct {
type PackageJSON struct {
Name string `json:"name"`
Version string `json:"version"`
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
}
func ParsePackageJson(path string) (RawPackage, error) {
func ParsePackageJson(path string) (PackageJSON, error) {
f, err := os.Open(path)
if os.IsNotExist(err) {
return RawPackage{}, errors.New("file doesn't exist.")
return PackageJSON{}, os.ErrNotExist
} else if err != nil {
return RawPackage{}, fmt.Errorf("could not read file: %v", err)
return PackageJSON{}, fmt.Errorf("could not read file: %v", err)
}
defer f.Close()
var p RawPackage
var p PackageJSON
if err := json.NewDecoder(f).Decode(&p); err != nil {
return RawPackage{}, fmt.Errorf("could not parse package.json file: %v", err)
return PackageJSON{}, fmt.Errorf("could not parse package.json file: %v", err)
}
return p, nil

@ -9,6 +9,7 @@ import (
)
func main() {
log.SetFlags(log.Lshortfile)
if len(os.Args) < 2 || os.Args[1] != "install" {
log.Fatal("gpm [install]")
}

@ -1,36 +1,16 @@
{
"name": "la-e2e",
"name": "test",
"version": "1.0.0",
"description": "Test Cafe based End to End integration tests",
"main": "smoke.js",
"scripts": {
"smoke": "testcafe puppeteer testCafeTests/smoke.js -S takeOnFail=true,fullPage=true -r spec,xunit:./test-results/xunit/smoke.xml ",
"regressions": "testcafe puppeteer testCafeTests/regressions.js -S takeOnFail=true,fullPage=true -r spec,xunit:./test-results/xunit/regressions.xml ",
"run-suite": "testcafe chrome:headless -e -r spec,xunit:./test-results/xunit/regressions.xml ",
"debug-smoke": "testcafe chrome testCafeTests/smoke.js --debug-on-fail --sf",
"smoke-selenium": "testcafe selenium:chrome -c 10 -q testCafeTests/smoke.js --ports 1337,1338 -e -r spec,json:./test-results/json/smoke.json,xunit:./test-results/xunit/smoke.xml --hostname",
"regressions-selenium": "testcafe selenium:chrome -c 10 -q testCafeTests/regressions.js -e -S takeOnFail=true,fullPage=true --ports 1337,1338 -r spec,json:./test-results/json/regressions.json,xunit:./test-results/xunit/regressions.xml --hostname",
"test-selenium": "testcafe selenium:chrome -c 10 -q chrome:headless testCafeTests/*.js -e -S takeOnFail=true,fullPage=true -r spec,json:./test-results/json/all.json,xunit:./test-results/xunit/all.xml --hostname",
"docker-build": "docker build --no-cache -t testcafe .",
"docker-dev": "docker run -it --rm --name tc --device /dev/net/tun -v $PWD:/opt/lae2e/:Z --cap-add=NET_ADMIN --cap-add=SYS_ADMIN --privileged -e UPDATE_SLACK=true -e RESULTS_SERVER=opsbot-local.liveauctioneers.com --dns 192.168.231.1 --entrypoint=bash testcafe",
"live-bidders": "testcafe -c 4 chrome:headless testCafeTests/livebidding.js -f \"Live bidding- two bidders\" -e",
"live-tests-visual": "testcafe chrome testCafeTests/livebidding.js -f \"Live bidding\" -e"
"test": "echo testing",
"build": "echo hello > world.txt"
},
"keywords": [],
"license": "UNLICENSED",
"dependencies": {
"base64-img": "^1.0.4",
"core-js": "^3.6.4",
"dotenv": "^8.2.0",
"random-email": "github:jaronaearle/random-email",
"request": "^2.88.0",
"testcafe": "^1.8.1"
"globby": "1.0.0"
},
"devDependencies": {
"@liveauctioneers/eslint-config-la-react": "^0.1.4",
"testcafe-browser-provider-puppeteer": "^1.5.2",
"testcafe-browser-provider-selenium": "^0.4.0",
"testcafe-reporter-json": "^2.2.0",
"testcafe-reporter-xunit": "^2.1.0"
"mocha": "*"
}
}

Loading…
Cancel
Save