diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..f2674cf --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,43 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang:1.13 + working_directory: /go/src/github.com/sitture/gauge-inprogress + steps: + - checkout + - run: + name: Get all dependencies + command: go get ./... + - run: + name: Run Tests + command: go test ./... -cover + - run: + name: Make Binaries + command: go run build/make.go --all-platforms + deploy: + docker: + - image: circleci/golang:1.13 + working_directory: /go/src/github.com/sitture/gauge-inprogress + steps: + - checkout + - run: + name: Get all dependencies + command: go get ./... + - run: + name: Install github-release + command: go get -v -u github.com/aktau/github-release + - run: + name: Build and Release + command: .circleci/release.sh +workflows: + version: 2 + build_and_deploy: + jobs: + - build + - deploy: + filters: + tags: + only: /.*/ + branches: + ignore: /.*/ \ No newline at end of file diff --git a/.circleci/release.sh b/.circleci/release.sh new file mode 100755 index 0000000..acb202b --- /dev/null +++ b/.circleci/release.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if [ -z "${GITHUB_TOKEN}" ]; then + echo "GITHUB_TOKEN is not set" + exit 1 +fi + +go run build/make.go --all-platforms +go run build/make.go --all-platforms --distro + +cd deploy/ +for i in `ls`; do + ${GOPATH}/bin/github-release upload \ + -u ${CIRCLE_PROJECT_USERNAME} \ + -r ${CIRCLE_PROJECT_REPONAME} \ + -t ${CIRCLE_TAG} \ + -n $i -f $i + if [ $? -ne 0 ];then + exit 1 + fi +done \ No newline at end of file diff --git a/go.sum b/go.sum index b0ea758..1233ffa 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dmotylev/goproperties v0.0.0-20140630191356-7cbffbaada47 h1:sP2APvSdZpfBiousrppBZNOvu+TE79Myq4kkmmrtSuI= github.com/dmotylev/goproperties v0.0.0-20140630191356-7cbffbaada47/go.mod h1:f2V6964+f0p8Asqy8mIK5cKyyVc6MP9PFzGVNRcnYJQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/getgauge/common v0.0.0-20200824023809-24587c106922 h1:/KwwglTNoofndO0RhEPY750erewl4uAL+WbfN+nLkVs= @@ -73,6 +75,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/handler.go b/handler.go index 7ab4464..12b0d6f 100644 --- a/handler.go +++ b/handler.go @@ -2,8 +2,9 @@ package main import ( "context" - "fmt" "github.com/sitture/gauge-inprogress/gauge_messages" + "github.com/sitture/gauge-inprogress/inprogress" + "github.com/sitture/gauge-inprogress/logger" "google.golang.org/grpc" ) @@ -12,7 +13,61 @@ type handler struct { } func (h *handler) GenerateDocs(c context.Context, m *gauge_messages.SpecDetails) (*gauge_messages.Empty, error) { - fmt.Printf("Succesfully converted specs to html") + logger.Debugf("In progress tags are set to %s.", inprogress.GetInProgressTags()) + specDirs := inprogress.GetSpecDirs() + logger.Debugf("Analysing specs under %s", specDirs) + allSpecs := inprogress.GetSpecs(m, inprogress.GetSpecFiles(specDirs)) + allScenarios := inprogress.GetScenarios(allSpecs) + inProgressSpecs := inprogress.GetInProgressSpecs(allSpecs) + inProgressScenarios := inprogress.GetInProgressScenarios(inProgressSpecs) + inProgressSpecsWithReason := inprogress.GetInProgressSpecsWithReason(inProgressSpecs) + inProgressScenariosWithReason := inprogress.GetInProgressScenariosWithReason(inProgressSpecsWithReason) + + if err := inprogress.WriteToFile(inProgressSpecs, inProgressScenariosWithReason); err != nil { + logger.Debugf("Could not generate the inprogress report file.") + } + + logger.Infof( + "\nIn progress Summary: %s\n", + inprogress.GetProjectDirName(), + ) + if inprogress.PercentOf(len(inProgressSpecs), len(allSpecs)) == 0 && inprogress.PercentOf(len(inProgressScenarios), len(allScenarios)) == 0 { + logger.Infof("No in progress scenarios found.") + } else { + logger.Infof( + "Specifications: %d/%d (%0.0f%%)", + len(inProgressSpecs), + len(allSpecs), + inprogress.PercentOf(len(inProgressSpecs), len(allSpecs)), + ) + specsWithoutReason := len(inProgressSpecs) - len(inProgressSpecsWithReason) + if specsWithoutReason > 0 { + logger.Infof( + " - No inprogress comments: %d/%d (%0.0f%%)", + specsWithoutReason, + len(inProgressSpecs), + inprogress.PercentOf(specsWithoutReason, len(inProgressSpecs)), + ) + } + logger.Infof( + "Scenarios: %d/%d (%0.0f%%)", + len(inProgressScenarios), + len(allScenarios), + inprogress.PercentOf(len(inProgressScenarios), len(allScenarios)), + ) + scenariosWithoutReason := len(inProgressScenarios) - len(inProgressScenariosWithReason) + if scenariosWithoutReason > 0 { + logger.Infof( + " - No inprogress comments: %d/%d (%0.0f%%)", + scenariosWithoutReason, + len(inProgressScenarios), + inprogress.PercentOf(scenariosWithoutReason, len(inProgressScenarios)), + ) + } + + } + logger.Infof("") + logger.Infof("Successfully generated inprogress report to => %s\n", inprogress.GetReportPath()) return &gauge_messages.Empty{}, nil } diff --git a/helper/helper.go b/helper/helper.go deleted file mode 100644 index d695471..0000000 --- a/helper/helper.go +++ /dev/null @@ -1,8 +0,0 @@ -package helper - -import "github.com/getgauge/common" - -func GetProjectRoot() string { - projectRoot, _ := common.GetProjectRoot() - return projectRoot -} diff --git a/inprogress/inprogress.go b/inprogress/inprogress.go new file mode 100644 index 0000000..06a432b --- /dev/null +++ b/inprogress/inprogress.go @@ -0,0 +1,336 @@ +package inprogress + +import ( + "fmt" + "github.com/getgauge/common" + "github.com/sitture/gauge-inprogress/gauge_messages" + "github.com/sitture/gauge-inprogress/logger" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +const ( + EnvFileExtensions = "gauge_spec_file_extensions" + EnvGaugeSpecDirs = "GAUGE_SPEC_DIRS" + EnvInProgressTags = "IN_PROGRESS_TAGS" + EnvInProgressConsoleOut = "IN_PROGRESS_CONSOLE_OUTPUT" + reportDirectory = "reports" + reportFile = "inprogress.md" +) + +func GetProjectRoot() string { + return os.Getenv(common.GaugeProjectRootEnv) +} + +var GetProjectDirName = func() string { + return path.Base(GetProjectRoot()) +} + +var GetInProgressTags = func() []string { + e := os.Getenv(EnvInProgressTags) + if e == "" { + e = "wip, in-progress" //this was earlier hardcoded, this is a failsafe if env isn't set + } + tags := strings.Split(strings.TrimSpace(e), ",") + var inProgressTags []string + for _, ext := range tags { + e := strings.TrimSpace(ext) + if e != "" { + inProgressTags = append(inProgressTags, e) + } + } + return inProgressTags +} + +var GetSpecDirs = func() []string { + return strings.Split(strings.TrimSpace(os.Getenv(EnvGaugeSpecDirs)), "||") +} + +var OutPutScenariosToConsole = func() bool { + value := os.Getenv(EnvInProgressConsoleOut) + if value == "true" { + return true + } + return false +} + +// findFilesIn Finds all the files in the directory of a given extension +func findFilesIn(dirRoot string, isValidFile func(path string) bool, shouldSkip func(path string, f os.FileInfo) bool) []string { + absRoot, _ := filepath.Abs(dirRoot) + files := common.FindFilesInDir(absRoot, isValidFile, shouldSkip) + return files +} + +func getProtoSpecs(m *gauge_messages.SpecDetails) []*gauge_messages.ProtoSpec { + specs := make([]*gauge_messages.ProtoSpec, 0) + for _, detail := range m.GetDetails() { + if detail.GetSpec() != nil { + specs = append(specs, detail.GetSpec()) + } + } + return specs +} + +func GetSpecs(m *gauge_messages.SpecDetails, files []string) []*gauge_messages.ProtoSpec { + specs := getProtoSpecs(m) + sortedSpecs := make([]*gauge_messages.ProtoSpec, 0) + specsMap := make(map[string]*gauge_messages.ProtoSpec, 0) + for _, spec := range specs { + specsMap[spec.GetFileName()] = spec + } + for _, file := range files { + spec, ok := specsMap[file] + if !ok { + spec = &gauge_messages.ProtoSpec{FileName: file, Tags: make([]string, 0), Items: make([]*gauge_messages.ProtoItem, 0)} + } + sortedSpecs = append(sortedSpecs, spec) + } + return sortedSpecs +} + +var GaugeSpecFileExtensions = func() []string { + e := os.Getenv(EnvFileExtensions) + if e == "" { + e = ".spec, .md" //this was earlier hardcoded, this is a failsafe if env isn't set + } + exts := strings.Split(strings.TrimSpace(e), ",") + var allowedExts = []string{} + for _, ext := range exts { + e := strings.TrimSpace(ext) + if e != "" { + allowedExts = append(allowedExts, e) + } + } + return allowedExts +} + +// IsValidSpecExtension Checks if the path has a spec file extension +func IsValidSpecExtension(path string) bool { + for _, ext := range GaugeSpecFileExtensions() { + if ext == strings.ToLower(filepath.Ext(path)) { + return true + } + } + return false +} + +// FindSpecFilesIn Finds spec files in the given directory +var FindSpecFilesIn = func(dir string) []string { + return findFilesIn(dir, IsValidSpecExtension, func(path string, f os.FileInfo) bool { + return false + }) +} + +// GetSpecFiles returns the list of spec files present at the given path. +// If the path itself represents a spec file, it returns the same. +var GetSpecFiles = func(paths []string) []string { + var specFiles []string + for _, path := range paths { + if !common.FileExists(path) { + logger.Fatalf("Specs directory %s does not exists.", path) + } + if common.DirExists(path) { + specFilesInPath := FindSpecFilesIn(path) + if len(specFilesInPath) < 1 { + logger.Fatalf("No specifications found in %s.", path) + } + specFiles = append(specFiles, specFilesInPath...) + } else if IsValidSpecExtension(path) { + f, _ := filepath.Abs(path) + specFiles = append(specFiles, f) + } + } + return specFiles +} + +func GetScenarios(specs []*gauge_messages.ProtoSpec) []*gauge_messages.ProtoScenario { + scenarios := make([]*gauge_messages.ProtoScenario, 0) + for _, spec := range specs { + for _, itemType := range spec.GetItems() { + if itemType.GetScenario() != nil { + scenarios = append(scenarios, itemType.GetScenario()) + } + } + } + return scenarios +} + +type InProgressSpec struct { + spec *gauge_messages.ProtoSpec + scenarios []*gauge_messages.ProtoScenario +} + +func (inProgressSpec *InProgressSpec) GetSpec() *gauge_messages.ProtoSpec { + return inProgressSpec.spec +} + +func (inProgressSpec *InProgressSpec) GetScenarios() []*gauge_messages.ProtoScenario { + return inProgressSpec.scenarios +} + +func getInProgressScenariosBySpec(spec *gauge_messages.ProtoSpec, tags []string) []*gauge_messages.ProtoScenario { + inProgressScenarios := make([]*gauge_messages.ProtoScenario, 0) + if containsInProgressTags(tags) { + for _, itemType := range spec.GetItems() { + scenario := itemType.GetScenario() + if scenario != nil { + inProgressScenarios = append(inProgressScenarios, scenario) + } + } + } + return inProgressScenarios +} + +func GetInProgressScenarios(specs map[string]InProgressSpec) []*gauge_messages.ProtoScenario { + inProgressScenarios := make([]*gauge_messages.ProtoScenario, 0) + for _, spec := range specs { + inProgressScenarios = append(inProgressScenarios, spec.GetScenarios()...) + } + return inProgressScenarios +} + +func GetInProgressSpecs(specs []*gauge_messages.ProtoSpec) map[string]InProgressSpec { + inProgressSpecs := make(map[string]InProgressSpec, 0) + for _, spec := range specs { + specKey := spec.GetSpecHeading() + if containsInProgressTags(spec.GetTags()) { + if _, exists := inProgressSpecs[specKey]; !exists { + inProgressSpec := InProgressSpec{spec: spec, scenarios: getInProgressScenariosBySpec(spec, spec.GetTags())} + inProgressSpecs[specKey] = inProgressSpec + } + } else { + inProgressScenarios := make([]*gauge_messages.ProtoScenario, 0) + if inProgressSpec, exists := inProgressSpecs[specKey]; exists { + inProgressScenarios = append(inProgressScenarios, inProgressSpec.scenarios...) + } + for _, itemType := range spec.GetItems() { + scenario := itemType.GetScenario() + if scenario != nil && containsInProgressTags(scenario.GetTags()) { + inProgressScenarios = append(inProgressScenarios, scenario) + } + } + if len(inProgressScenarios) > 0 { + delete(inProgressSpecs, specKey) + inProgressSpec := InProgressSpec{spec: spec, scenarios: inProgressScenarios} + inProgressSpecs[specKey] = inProgressSpec + } + } + } + return inProgressSpecs +} + +func GetInProgressSpecsWithReason(specs map[string]InProgressSpec) map[string]InProgressSpec { + inProgressSpecs := make(map[string]InProgressSpec, 0) + for specKey, spec := range specs { + for _, specItem := range spec.GetSpec().GetItems() { + if specItem.GetItemType() == gauge_messages.ProtoItem_Comment && containsInProgressPrefix(specItem.GetComment().GetText()) { + if _, exists := inProgressSpecs[specKey]; !exists { + inProgressSpec := InProgressSpec{spec: spec.GetSpec(), scenarios: spec.GetScenarios()} + inProgressSpecs[specKey] = inProgressSpec + } + } + } + for _, scenario := range spec.GetScenarios() { + for _, scenItem := range scenario.GetScenarioItems() { + if scenItem.GetItemType() == gauge_messages.ProtoItem_Comment && containsInProgressPrefix(scenItem.GetComment().GetText()) { + if _, exists := inProgressSpecs[specKey]; !exists { + inProgressSpec := InProgressSpec{spec: spec.GetSpec(), scenarios: spec.GetScenarios()} + inProgressSpecs[specKey] = inProgressSpec + } + } + } + } + } + return inProgressSpecs +} + +type ScenarioWithReason struct { + Scenario *gauge_messages.ProtoScenario + Reason string +} + +func GetInProgressScenariosWithReason(specs map[string]InProgressSpec) map[string]ScenarioWithReason { + inProgressScenarios := make(map[string]ScenarioWithReason, 0) + for _, spec := range specs { + if containsInProgressTags(spec.GetSpec().GetTags()) { + for _, specItem := range spec.GetSpec().GetItems() { + if specItem.GetItemType() == gauge_messages.ProtoItem_Comment && containsInProgressPrefix(specItem.GetComment().GetText()) { + for _, scenario := range spec.GetScenarios() { + key := scenario.GetScenarioHeading() + inProgressScenarios[key] = ScenarioWithReason{scenario, specItem.GetComment().GetText()} + } + } + } + } else { + for _, scenario := range spec.GetScenarios() { + key := scenario.GetScenarioHeading() + for _, scenItem := range scenario.GetScenarioItems() { + if scenItem.GetItemType() == gauge_messages.ProtoItem_Comment && containsInProgressPrefix(scenItem.GetComment().GetText()) { + inProgressScenarios[key] = ScenarioWithReason{scenario, scenItem.GetComment().GetText()} + } + } + } + } + } + return inProgressScenarios +} + +var GetReportPath = func() string { + return path.Join(GetProjectRoot(), reportDirectory, reportFile) +} + +func WriteToFile(inProgressSpecs map[string]InProgressSpec, inProgressScenariosWithReason map[string]ScenarioWithReason) (error error) { + console := OutPutScenariosToConsole() + file, err := os.Create(GetReportPath()) + if err != nil { + logger.Fatalf("Unable to create data.js file") + } + for _, spec := range inProgressSpecs { + specLine := fmt.Sprintf("# %s // %s", spec.GetSpec().GetSpecHeading(), + filepath.Base(spec.GetSpec().GetFileName())) + _, error = file.WriteString(specLine + "\n") + if console { + logger.Infof(specLine) + } + for _, scenario := range spec.GetScenarios() { + scenarioLine := fmt.Sprintf(" ## %s", scenario.GetScenarioHeading()) + _, error = file.WriteString(scenarioLine + "\n") + if console { + logger.Infof(scenarioLine) + } + inProgressReason := inProgressScenariosWithReason[scenario.GetScenarioHeading()].Reason + if len(strings.TrimSpace(inProgressReason)) > 0 { + reasonLine := fmt.Sprintf(" - %s", inProgressReason) + _, error = file.WriteString(reasonLine + "\n") + if console { + logger.Infof(reasonLine) + } + } + } + } + error = file.Close() + return +} + +func containsInProgressPrefix(comment string) bool { + var inProgressRegex = regexp.MustCompile(`^in.?progress|//.*in.?progress`) + return inProgressRegex.MatchString(strings.ToLower(strings.TrimSpace(comment))) +} + +func containsInProgressTags(tags []string) bool { + for _, inProgressTag := range GetInProgressTags() { + for _, tag := range tags { + if strings.TrimSpace(tag) == inProgressTag { + return true + } + } + } + return false +} + +func PercentOf(part int, total int) float64 { + return (float64(part) * float64(100)) / float64(total) +} diff --git a/inprogress.go b/main.go similarity index 78% rename from inprogress.go rename to main.go index 764918f..67d359b 100644 --- a/inprogress.go +++ b/main.go @@ -2,7 +2,7 @@ package main import ( "github.com/sitture/gauge-inprogress/gauge_messages" - "github.com/sitture/gauge-inprogress/helper" + "github.com/sitture/gauge-inprogress/inprogress" "github.com/sitture/gauge-inprogress/logger" "google.golang.org/grpc" "net" @@ -18,14 +18,14 @@ const ( ) func main() { - os.Chdir(helper.GetProjectRoot()) + os.Chdir(inprogress.GetProjectRoot()) address, err := net.ResolveTCPAddr("tcp", GaugeHost) if err != nil { - logger.Fatalf("failed to start server.", err) + logger.Fatalf("failed to start server. %s", err.Error()) } listener, err := net.ListenTCP("tcp", address) if err != nil { - logger.Fatalf("failed to start server.", err) + logger.Fatalf("failed to start server. %s", err.Error()) } server := grpc.NewServer(grpc.MaxRecvMsgSize(msgSize)) handler := &handler{server: server}