Repository: goadesign/goa
Pull request #2433 (2019-12-14 19:40:10)

Set up Github actions
Replace TravisCI and CircleCI in branch `v3` with Github actions.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..09a29558
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,59 @@
+name: Build
+on: [push]
+jobs:
+
+  Linux:
+    name: Linux
+    runs-on: ubuntu-latest
+    steps:
+
+    - name: Set up Go 1.13
+      uses: actions/setup-go@v1
+      with:
+        go-version: 1.13
+      id: go
+
+    - name: Check out code into the Go module directory
+      uses: actions/checkout@v2    
+
+    - name: Extract branch name
+      run: echo "::set-output name=branch::$(echo ${GITHUB_REF#refs/heads/})"
+      id: extract_branch
+
+    - name: Add $GOPATH/bin to PATH
+      run: echo "::add-path::$(go env GOPATH)/bin"
+      id: setup_path
+
+    - name: Build
+      run: make travis
+      env:
+        GOA_BRANCH: ${{ steps.extract_branch.outputs.branch }}
+
+  Windows:
+    name: Windows
+    runs-on: windows-latest
+    steps:
+
+    - name: Set up Go 1.13
+      uses: actions/setup-go@v1
+      with:
+        go-version: 1.13
+      id: go
+
+    - name: Check out code into the Go module directory
+      uses: actions/checkout@v2    
+
+    - name: Extract branch name
+      shell: bash
+      run: echo "::set-output name=branch::$(echo ${GITHUB_REF#refs/heads/})"
+      id: extract_branch
+
+    - name: Add $GOPATH/bin to PATH
+      shell: bash
+      run: echo "::add-path::$(go env GOPATH)/bin"
+      id: setup_path
+
+    - name: Build
+      run: make travis
+      env:
+        GOA_BRANCH: ${{ steps.extract_branch.outputs.branch }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 29fa900e..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: go
-dist: xenial
-go:
-- 1.13.x
-install:
-- export PATH=${PATH}:${HOME}/gopath/bin
-script:
-- export PATH=${PATH}:${HOME}/bin
-- cd ../../.. && mkdir goa.design && cp -r github.com/goadesign/goa goa.design/goa
-- cd goa.design/goa
-# https://graysonkoonce.com/getting-the-current-branch-name-during-a-pull-request-in-travis-ci/
-- export GOA_BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi) && echo $GOA_BRANCH
-- make travis
-notifications:
-  slack:
-    secure: bMYXaoSEGoNdqR0t1VnMAv/4V9PSOhEWyekdJM7p9WmKjJi2yKy0k77uRmwf+5Mrz5GLs3CkZnDha/8cSFld3KEN9SC6QYmIBF/1Pd/5mKHFQOI81i7sTlhrdMv897+6sofEtbBNq1jffhVGVttbMrMWwCTNZu0NrCGBVsDmb44=
diff --git a/Makefile b/Makefile
index b653cf39..cb6c703f 100644
--- a/Makefile
+++ b/Makefile
@@ -18,17 +18,7 @@ BUILD=8
 
 GOOS=$(shell go env GOOS)
 GO_FILES=$(shell find . -type f -name '*.go')
-DIR=$(shell pwd)
-
-ifeq ($(GOOS),windows)
-EXAMPLES_DIR="$(GOPATH)\src\goa.design\examples"
-PLUGINS_DIR="$(GOPATH)\src\goa.design\plugins"
-GOBIN="$(GOPATH)\bin"
-else
-EXAMPLES_DIR=$(GOPATH)/src/goa.design/examples
-PLUGINS_DIR=$(GOPATH)/src/goa.design/plugins
-GOBIN=$(GOPATH)/bin
-endif
+GOPATH=$(shell go env GOPATH)
 
 # Only list test and build dependencies
 # Standard dependencies are installed via go get
@@ -56,19 +46,22 @@ PROTOC_EXEC=$(PROTOC)/bin/protoc
 		ifeq ($(GOOS),windows)
 PROTOC=protoc-$(PROTOC_VERSION)-win32
 PROTOC_EXEC="$(PROTOC)\bin\protoc.exe"
+GOPATH:=$(subst \,/,$(GOPATH))
 		endif
 	endif
 endif
 depend:
 	@go get -v $(DEPEND)
-	@env GO111MODULE=off go get github.com/hashicorp/go-getter/cmd/go-getter && \
-		go-getter https://github.com/google/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC).zip $(PROTOC) && \
-		cp $(PROTOC_EXEC) $(GOBIN) && \
+	@echo Installing protoc
+	@env GO111MODULE=off go get github.com/hashicorp/go-getter/cmd/go-getter
+	go-getter https://github.com/google/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC).zip $(PROTOC)
+	@cp $(PROTOC_EXEC) $(GOPATH)/bin && \
 		rm -r $(PROTOC) && \
 		echo "`protoc --version`"
 	@go get -t -v ./...
 
 lint:
+ifneq ($(GOOS),windows)
 	@if [ "`goimports -l $(GO_FILES) | tee /dev/stderr`" ]; then \
 		echo "^ - Repo contains improperly formatted go files" && echo && exit 1; \
 	fi
@@ -78,6 +71,7 @@ lint:
 	@if [ "`staticcheck -checks all ./... | grep -v ".pb.go" | tee /dev/stderr`" ]; then \
 		echo "^ - staticcheck errors!" && echo && exit 1; \
 	fi
+endif
 
 test:
 	env GO111MODULE=on go test ./...
diff --git a/README.md b/README.md
index c5f1027c..9d801efc 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,7 @@ Goa is a framework for building micro-services and APIs in Go using a unique
 design-first approach.
 
 ---
-[![Build Status](https://travis-ci.org/goadesign/goa.svg?branch=v3)](https://travis-ci.org/goadesign/goa)
-[![Windows Build status](https://ci.appveyor.com/api/projects/status/vixp37loj5i6qmaf/branch/v3?svg=true)](https://ci.appveyor.com/project/RaphaelSimon/goa-oqtis/branch/v3)
+[![Build Status](https://github.com/goadesign/goa/workflows/Build/badge.svg?branch=v3)](https://github.com/goadesign/goa/actions?query=branch%3Av3)
 [![Godoc](https://godoc.org/goa.design/goa?status.svg)](https://godoc.org/goa.design/goa)
 [![Slack](https://img.shields.io/badge/slack-gophers-orange.svg?style=flat)](https://gophers.slack.com/messages/goa/)
 
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index e6384007..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-version: "{build}"
-
-os: Windows Server 2012 R2
-
-environment:
-  GOPATH: C:\gopath
-
-clone_folder: c:\gopath\src\goa.design\goa
-
-install:
-  - ps: >-
-      $goDistPath = "$env:TEMP\go1.13.3.windows-amd64.zip"; (New-Object Net.WebClient).DownloadFile('https://dl.google.com/go/go1.13.3.windows-amd64.zip', $goDistPath); 7z x $goDistPath -oC:\go1133-x64 | Out-Null; [IO.Directory]::Move('C:\go1133-x64\go', 'C:\go113'); Remove-Item 'C:\go1133-x64' -Recurse -Force; del $goDistPath
-  - set GOROOT=c:\go113
-  - set PATH=%GOROOT%\bin;c:\msys64\usr\bin;%GOPATH%\bin;%PATH%
-  - go version
-
-build_script:
-  - if "%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" == "" ( make travis GOA_BRANCH="%APPVEYOR_REPO_BRANCH%" ) else ( make travis GOA_BRANCH="%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%" )
-
-test: off
-
-deploy: off
diff --git a/cmd/goa/gen.go b/cmd/goa/gen.go
index 6237c45c..d57305a1 100644
--- a/cmd/goa/gen.go
+++ b/cmd/goa/gen.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"go/build"
 	"go/parser"
 	"go/token"
 	"io/ioutil"
@@ -152,6 +153,9 @@ func (g *Generator) Run() ([]string, error) {
 	{
 		args := make([]string, len(os.Args)-1)
 		gopaths := filepath.SplitList(os.Getenv("GOPATH"))
+		if len(gopaths) == 0 {
+			gopaths = []string{build.Default.GOPATH}
+		}
 		for i, a := range os.Args[1:] {
 			for _, p := range gopaths {
 				if strings.Contains(a, p) {
diff --git a/codegen/service/convert.go b/codegen/service/convert.go
index 61b97e1f..50818c9a 100644
--- a/codegen/service/convert.go
+++ b/codegen/service/convert.go
@@ -2,6 +2,7 @@ package service
 
 import (
 	"fmt"
+	"go/build"
 	"os"
 	"path"
 	"path/filepath"
@@ -87,7 +88,11 @@ func commonPath(sep byte, paths ...string) string {
 // ("goa.design/goa/vendor/some/package") for vendored packages
 // instead the source import path ("some/package")
 func getPkgImport(pkg, cwd string) string {
-	gosrc := path.Join(filepath.ToSlash(os.Getenv("GOPATH")), "src")
+	gopath := os.Getenv("GOPATH")
+	if gopath == "" {
+		gopath = build.Default.GOPATH
+	}
+	gosrc := path.Join(filepath.ToSlash(gopath), "src")
 	cwd = filepath.ToSlash(cwd)
 
 	// check for go modules
diff --git a/expr/http_endpoint.go b/expr/http_endpoint.go
index f1c53818..6bcd0359 100644
--- a/expr/http_endpoint.go
+++ b/expr/http_endpoint.go
@@ -192,18 +192,23 @@ func (e *HTTPEndpointExpr) Prepare() {
 			c.Prepare()
 			if !e.HasAbsoluteRoutes() {
 				headers.Merge(c.Headers)
-				params.Merge(c.PathParams())
+				cpp := c.PathParams()
+				params.Merge(cpp)
 
 				// Inherit attributes for path params from parent service
-				WalkMappedAttr(c.PathParams(), func(name, _ string, _ *AttributeExpr) error {
+				WalkMappedAttr(cpp, func(name, _ string, _ *AttributeExpr) error {
 					if att := c.MethodExpr.Payload.Find(name); att != nil {
 						if e.MethodExpr.Payload.Type == Empty {
 							e.MethodExpr.Payload.Type = &Object{}
 						}
-						if o := AsObject(e.MethodExpr.Payload.Type); o != nil {
-							if o.Attribute(name) == nil {
-								o.Set(name, att)
+						if o := AsObject(e.MethodExpr.Payload.Type); o != nil && o.Attribute(name) == nil {
+							if c.MethodExpr.Payload.IsRequired(name) {
+								if e.MethodExpr.Payload.Validation == nil {
+									e.MethodExpr.Payload.Validation = &ValidationExpr{}
+								}
+								e.MethodExpr.Payload.Validation.AddRequired(name)
 							}
+							o.Set(name, att)
 						}
 					}
 					return nil


Test patch:
diff --git a/codegen/service/convert_test.go b/codegen/service/convert_test.go
index e681eb07..8fe82f77 100644
--- a/codegen/service/convert_test.go
+++ b/codegen/service/convert_test.go
@@ -1,6 +1,7 @@
 package service
 
 import (
+	"go/build"
 	"os"
 	"reflect"
 	"testing"
@@ -59,7 +60,11 @@ func TestCommonPath(t *testing.T) {
 }
 
 func TestPkgImport(t *testing.T) {
-	cwd := os.Getenv("GOPATH") + "/src/goa.design/goa/codegen/service"
+	gopath := os.Getenv("GOPATH")
+	if gopath == "" {
+		gopath = build.Default.GOPATH
+	}
+	cwd := gopath + "/src/goa.design/goa/codegen/service"
 	goModCwd := "/home/user/project/goa/codegen/service"
 	cases := []struct {
 		Name           string
diff --git a/expr/http_endpoint_test.go b/expr/http_endpoint_test.go
index e9d1483a..7b9e3a55 100644
--- a/expr/http_endpoint_test.go
+++ b/expr/http_endpoint_test.go
@@ -168,6 +168,24 @@ func TestHTTPEndpointValidation(t *testing.T) {
 	}
 }
 
+func TestHTTPEndpointParentRequired(t *testing.T) {
+	root := expr.RunDSL(t, testdata.EndpointHasParent)
+	svc := root.Service("Child")
+	if svc == nil {
+		t.Fatal(`unexpected error, service "Child" not found`)
+	}
+	m := svc.Method("Method")
+	if m == nil {
+		t.Fatal(`unexpected error, method "Method" not found`)
+	}
+	if !m.Payload.IsRequired("ancestor_id") {
+		t.Errorf(`expected "ancestor_id" is required, but not so`)
+	}
+	if !m.Payload.IsRequired("parent_id") {
+		t.Errorf(`expected "parent_id" is required, but not so`)
+	}
+}
+
 func TestHTTPEndpointFinalization(t *testing.T) {
 	cases := map[string]struct {
 		DSL          func()
diff --git a/expr/testdata/endpoint_dsls.go b/expr/testdata/endpoint_dsls.go
index d304d6dc..df34fea9 100644
--- a/expr/testdata/endpoint_dsls.go
+++ b/expr/testdata/endpoint_dsls.go
@@ -252,15 +252,35 @@ var EndpointExtendToken = func() {
 }
 
 var EndpointHasParent = func() {
+	Service("Ancestor", func() {
+		HTTP(func() {
+			Path("/ancestor")
+			CanonicalMethod("Method")
+		})
+		Method("Method", func() {
+			Payload(func() {
+				Attribute("ancestor_id", Int)
+				Attribute("query_0", String)
+				Required("ancestor_id")
+			})
+			HTTP(func() {
+				GET("/{ancestor_id}")
+				Param("query_0")
+			})
+		})
+
+	})
 	Service("Parent", func() {
 		HTTP(func() {
 			Path("/parents")
 			CanonicalMethod("Method")
+			Parent("Ancestor")
 		})
 		Method("Method", func() {
 			Payload(func() {
 				Attribute("parent_id", Int)
 				Attribute("query_1", String)
+				Required("parent_id")
 			})
 			HTTP(func() {
 				GET("/{parent_id}")
diff --git a/http/codegen/openapi_test.go b/http/codegen/openapi_test.go
index 9c20b2bf..5f03532e 100644
--- a/http/codegen/openapi_test.go
+++ b/http/codegen/openapi_test.go
@@ -12,6 +12,7 @@ import (
 	"text/template"
 
 	"github.com/go-openapi/loads"
+	"goa.design/goa/v3/codegen"
 	"goa.design/goa/v3/http/codegen/openapi"
 	"goa.design/goa/v3/http/codegen/testdata"
 )
@@ -121,11 +122,12 @@ func TestSections(t *testing.T) {
 					}
 
 					want, err := ioutil.ReadFile(golden)
+					want = bytes.Replace(want, []byte{'\r', '\n'}, []byte{'\n'}, -1)
 					if err != nil {
 						t.Fatalf("failed to read golden file: %s", err)
 					}
 					if !bytes.Equal(buf.Bytes(), want) {
-						t.Errorf("result do not match the golden file:\n--BEGIN--\n%s\n--END--\n", buf.Bytes())
+						t.Errorf("result does not match the golden file, diff:\n%s\nGot bytes:\n%x\nExpected bytes:\n%x\n", codegen.Diff(t, buf.String(), string(want)), buf.Bytes(), want)
 					}
 				})
 			}
@@ -187,6 +189,7 @@ func TestValidations(t *testing.T) {
 					}
 
 					want, err := ioutil.ReadFile(golden)
+					want = bytes.Replace(want, []byte{'\r', '\n'}, []byte{'\n'}, -1)
 					if err != nil {
 						t.Fatalf("failed to read golden file: %s", err)
 					}
@@ -251,6 +254,7 @@ func TestExtensions(t *testing.T) {
 					}
 
 					want, err := ioutil.ReadFile(golden)
+					want = bytes.Replace(want, []byte{'\r', '\n'}, []byte{'\n'}, -1)
 					if err != nil {
 						t.Fatalf("failed to read golden file: %s", err)
 					}