From 90854eb4ec7d540de2ac4c76c3fdf6d343396f64 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 25 Feb 2020 12:49:58 +0100 Subject: [PATCH 01/26] skip tests support --- instrumentation/testing/config/testing.go | 28 +++++++++++++++++ instrumentation/testing/init.go | 3 ++ instrumentation/testing/testing.go | 38 +++++++++++++++++++++-- reflection/reflect.go | 14 +++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 instrumentation/testing/config/testing.go diff --git a/instrumentation/testing/config/testing.go b/instrumentation/testing/config/testing.go new file mode 100644 index 00000000..fb880815 --- /dev/null +++ b/instrumentation/testing/config/testing.go @@ -0,0 +1,28 @@ +package config + +import ( + "sync" +) + +var ( + testsToSkip map[string]struct{} + + m sync.RWMutex +) + +func SetFqnToSkip(fqns ...string) { + m.Lock() + defer m.Unlock() + + testsToSkip = map[string]struct{}{} + for _, val := range fqns { + testsToSkip[val] = struct{}{} + } +} + +func GetSkipMap() map[string]struct{} { + m.RLock() + defer m.RUnlock() + + return testsToSkip +} diff --git a/instrumentation/testing/init.go b/instrumentation/testing/init.go index 58cda8e6..120a7d0f 100644 --- a/instrumentation/testing/init.go +++ b/instrumentation/testing/init.go @@ -31,6 +31,9 @@ func Init(m *testing.M) { tests = append(tests, testing.InternalTest{ Name: test.Name, F: func(t *testing.T) { // Creating a new test function as an indirection of the original test + if shouldSkipTest(t, funcPointer) { + return + } addAutoInstrumentedTest(t) tStruct := StartTestFromCaller(t, funcPointer) defer tStruct.end() diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index c1e30124..f599f9d7 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -16,6 +16,7 @@ import ( "go.undefinedlabs.com/scopeagent/errors" "go.undefinedlabs.com/scopeagent/instrumentation" "go.undefinedlabs.com/scopeagent/instrumentation/logging" + "go.undefinedlabs.com/scopeagent/instrumentation/testing/config" "go.undefinedlabs.com/scopeagent/reflection" "go.undefinedlabs.com/scopeagent/runner" "go.undefinedlabs.com/scopeagent/tags" @@ -134,11 +135,19 @@ func (test *Test) Context() context.Context { // Runs an auto instrumented sub test func (test *Test) Run(name string, f func(t *testing.T)) bool { + pc, _, _, _ := runtime.Caller(1) if test.span == nil { // No span = not instrumented - return test.t.Run(name, f) + return test.t.Run(name, func(cT *testing.T) { + if shouldSkipTest(cT, pc) { + return + } + f(cT) + }) } - pc, _, _, _ := runtime.Caller(1) return test.t.Run(name, func(childT *testing.T) { + if shouldSkipTest(childT, pc) { + return + } addAutoInstrumentedTest(childT) childTest := StartTestFromCaller(childT, pc) defer childTest.end() @@ -279,3 +288,28 @@ func addAutoInstrumentedTest(t *testing.T) { defer autoInstrumentedTestsMutex.Unlock() autoInstrumentedTests[t] = true } + +// Should skip test +func shouldSkipTest(t *testing.T, pc uintptr) bool { + fullTestName := runner.GetOriginalTestName(t.Name()) + testNameSlash := strings.IndexByte(fullTestName, '/') + funcName := fullTestName + if testNameSlash >= 0 { + funcName = fullTestName[:testNameSlash] + } + + funcFullName := runtime.FuncForPC(pc).Name() + funcNameIndex := strings.LastIndex(funcFullName, funcName) + if funcNameIndex < 1 { + funcNameIndex = len(funcFullName) + } + packageName := funcFullName[:funcNameIndex-1] + + fqn := fmt.Sprintf("%s.%s", packageName, fullTestName) + skipMap := config.GetSkipMap() + if _, ok := skipMap[fqn]; ok { + reflection.SkipAndFinishTest(t) + return true + } + return false +} diff --git a/reflection/reflect.go b/reflection/reflect.go index 46a01bdd..40cdbf58 100644 --- a/reflection/reflect.go +++ b/reflection/reflect.go @@ -149,3 +149,17 @@ func GetBenchmarkResult(b *testing.B) (*testing.BenchmarkResult, error) { return nil, err } } + +func SkipAndFinishTest(t *testing.T) { + mu := GetTestMutex(t) + if mu != nil { + mu.Lock() + defer mu.Unlock() + } + if pointer, err := GetFieldPointerOf(t, "skipped"); err == nil { + *(*bool)(pointer) = true + } + if pointer, err := GetFieldPointerOf(t, "finished"); err == nil { + *(*bool)(pointer) = true + } +} From ce741c58fa550f3b7a5a6e5de3a3834fe7fa69ed Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 17 Mar 2020 17:38:45 +0100 Subject: [PATCH 02/26] fix import --- instrumentation/testing/testing.go | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index f599f9d7..3453b5f0 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -2,6 +2,7 @@ package testing import ( "context" + "fmt" "reflect" "regexp" "runtime" From 4b794b192e9d218ce65d567dec7a2494416f8b71 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Mon, 27 Apr 2020 22:14:38 +0200 Subject: [PATCH 03/26] remote config preparation --- agent/remote_config.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 agent/remote_config.go diff --git a/agent/remote_config.go b/agent/remote_config.go new file mode 100644 index 00000000..afd38c0a --- /dev/null +++ b/agent/remote_config.go @@ -0,0 +1,50 @@ +package agent + +import ( + "log" + "net/http" +) + +type ( + RemoteConfig struct { + request remoteConfigRequest + response *remoteConfigResponse + apiKey string + apiEndpoint string + version string + userAgent string + debugMode bool + url string + client *http.Client + logger *log.Logger + } + + + remoteConfigRequest struct { + Repository string `json:"repository" msgpack:"repository"` + Commit string `json:"commit" msgpack:"commit"` + Service string `json:"service" msgpack:"service"` + Dependencies map[string]string `json:"dependencies" msgpack:"dependencies"` + } + remoteConfigResponse struct { + Cached []CachedTests `json:"cached" msgpack:"cached"` + } + CachedTests struct { + TestSuite string `json:"test_suite" msgpack:"test_suite"` + TestName string `json:"test_name" msgpack:"test_name"` + } +) + +func NewRemoteConfig(agent *Agent) *RemoteConfig { + r := new(RemoteConfig) + //r.request.Repository = agent.repos + r.apiEndpoint = agent.apiEndpoint + r.apiKey = agent.apiKey + r.version = agent.version + r.userAgent = agent.userAgent + r.debugMode = agent.debugMode + r.logger = agent.logger + r.url = agent.getUrl("api/agent/config") + r.client = &http.Client{} + return r +} From d52f174a3d1129d061c1a17fc70a9dbce1b8b5a0 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 19:44:00 +0200 Subject: [PATCH 04/26] config request --- agent/agent.go | 3 + agent/remote_config.go | 205 +++++++++++++++++++++++++++++++++-------- 2 files changed, 172 insertions(+), 36 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 2c939460..47d1e478 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -365,6 +365,9 @@ func NewAgent(options ...Option) (*Agent, error) { opentracing.SetGlobalTracer(agent.Tracer()) } + cfg := agent.loadRemoteConfig() + _ = cfg + return agent, nil } diff --git a/agent/remote_config.go b/agent/remote_config.go index afd38c0a..a23f8d19 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -1,50 +1,183 @@ package agent import ( - "log" + "bytes" + "crypto/sha1" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "github.com/mitchellh/go-homedir" + "go.undefinedlabs.com/scopeagent/tags" + "io/ioutil" "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "time" ) -type ( - RemoteConfig struct { - request remoteConfigRequest - response *remoteConfigResponse - apiKey string - apiEndpoint string - version string - userAgent string - debugMode bool - url string - client *http.Client - logger *log.Logger +func (a *Agent) loadRemoteConfig() map[string]interface{} { + if a == nil || a.metadata == nil { + return nil } + var ( + path string + err error + configRequest = map[string]interface{}{} + ) + addElementToMapIfEmpty(configRequest, tags.Repository, a.metadata[tags.Repository]) + addElementToMapIfEmpty(configRequest, tags.Commit, a.metadata[tags.Commit]) + addElementToMapIfEmpty(configRequest, tags.Service, a.metadata[tags.Service]) + addElementToMapIfEmpty(configRequest, tags.Dependencies, a.metadata[tags.Dependencies]) + path, err = getLocalConfigurationPath(configRequest) + if err == nil { + file, lerr := os.Open(path) + err = lerr + if lerr == nil { + defer file.Close() + fileBytes, lerr := ioutil.ReadAll(file) + err = lerr + if lerr == nil { + var res map[string]interface{} + if lerr = json.Unmarshal(fileBytes, &res); lerr == nil { + return res + } else { + err = lerr + } + } + } + } + if err != nil { + a.logger.Printf("Error loading local configuration: %v", err) + } - remoteConfigRequest struct { - Repository string `json:"repository" msgpack:"repository"` - Commit string `json:"commit" msgpack:"commit"` - Service string `json:"service" msgpack:"service"` - Dependencies map[string]string `json:"dependencies" msgpack:"dependencies"` + client := &http.Client{} + curl := a.getUrl("api/agent/config") + payload, err := encodePayload(configRequest) + if err != nil { + a.logger.Printf("Error encoding payload: %v", err) } - remoteConfigResponse struct { - Cached []CachedTests `json:"cached" msgpack:"cached"` + payloadBytes := payload.Bytes() + + var ( + lastError error + status string + statusCode int + bodyData []byte + ) + for i := 0; i <= numOfRetries; i++ { + req, err := http.NewRequest("POST", curl, bytes.NewBuffer(payloadBytes)) + if err != nil { + a.logger.Printf("Error creating new request: %v", err) + return nil + } + req.Header.Set("User-Agent", a.userAgent) + req.Header.Set("Content-Type", "application/msgpack") + req.Header.Set("Content-Encoding", "gzip") + req.Header.Set("X-Scope-ApiKey", a.apiKey) + + if a.debugMode { + if i == 0 { + a.logger.Println("sending payload") + } else { + a.logger.Printf("sending payload [retry %d]", i) + } + } + + resp, err := client.Do(req) + if err != nil { + if v, ok := err.(*url.Error); ok { + // Don't retry if the error was due to TLS cert verification failure. + if _, ok := v.Err.(x509.UnknownAuthorityError); ok { + a.logger.Printf("error: http client returns: %s", err.Error()) + return nil + } + } + + lastError = err + a.logger.Printf("client error '%s', retrying in %d seconds", err.Error(), retryBackoff/time.Second) + time.Sleep(retryBackoff) + continue + } + + statusCode = resp.StatusCode + status = resp.Status + if resp.Body != nil && resp.Body != http.NoBody { + body, err := ioutil.ReadAll(resp.Body) + if err == nil { + bodyData = body + } + } + if err := resp.Body.Close(); err != nil { // We can't defer inside a for loop + a.logger.Printf("error: closing the response body. %s", err.Error()) + } + + if statusCode == 0 || statusCode >= 400 { + lastError = errors.New(fmt.Sprintf("error from API [status: %s]: %s", status, string(bodyData))) + } + + // Check the response code. We retry on 500-range responses to allow + // the server time to recover, as 500's are typically not permanent + // errors and may relate to outages on the server side. This will catch + // invalid response codes as well, like 0 and 999. + if statusCode == 0 || (statusCode >= 500 && statusCode != 501) { + a.logger.Printf("error: [status code: %d], retrying in %d seconds", statusCode, retryBackoff/time.Second) + time.Sleep(retryBackoff) + continue + } + + if i > 0 { + a.logger.Printf("payload was sent successfully after retry.") + } + break } - CachedTests struct { - TestSuite string `json:"test_suite" msgpack:"test_suite"` - TestName string `json:"test_name" msgpack:"test_name"` + + if statusCode != 0 && statusCode < 400 && lastError == nil { + var resp map[string]interface{} + if err := json.Unmarshal(bodyData, &resp); err == nil { + if path != "" { + if err := ioutil.WriteFile(path, bodyData, 0755); err != nil { + a.logger.Printf("Error writing json file: %v", err) + } + } + return resp + } else { + a.logger.Printf("Error unmarshalling msgpack: %v", err) + } } -) + return nil +} -func NewRemoteConfig(agent *Agent) *RemoteConfig { - r := new(RemoteConfig) - //r.request.Repository = agent.repos - r.apiEndpoint = agent.apiEndpoint - r.apiKey = agent.apiKey - r.version = agent.version - r.userAgent = agent.userAgent - r.debugMode = agent.debugMode - r.logger = agent.logger - r.url = agent.getUrl("api/agent/config") - r.client = &http.Client{} - return r +func getLocalConfigurationPath(metadata map[string]interface{}) (string, error) { + homeDir, err := homedir.Dir() + if err != nil { + return "", err + } + data, err := json.Marshal(metadata) + if err != nil { + return "", err + } + hash := fmt.Sprintf("%x", sha1.Sum(data)) + + var folder string + if runtime.GOOS == "windows" { + folder = fmt.Sprintf("%s/AppData/Roaming/scope/cache", homeDir) + } else { + folder = fmt.Sprintf("%s/.scope/cache", homeDir) + } + + if _, err := os.Stat(folder); err == nil { + return filepath.Join(folder, hash), nil + } else if os.IsNotExist(err) { + err = os.MkdirAll(folder, 0755) + if err != nil { + return "", err + } + return filepath.Join(folder, hash), nil + } else { + return "", err + } } From e4de8cfd5d9c1a38f42bb199895c49ea7c96c30e Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 22:42:04 +0200 Subject: [PATCH 05/26] remote configuration algorithm and implementation --- agent/agent.go | 4 +- agent/remote_config.go | 105 ++++++++++++++-------- instrumentation/testing/config/testing.go | 26 +++--- instrumentation/testing/init.go | 2 +- instrumentation/testing/testing.go | 12 +-- instrumentation/tracer.go | 21 ++++- 6 files changed, 111 insertions(+), 59 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 47d1e478..9953b1eb 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -361,13 +361,11 @@ func NewAgent(options ...Option) (*Agent, error) { instrumentation.SetTracer(agent.tracer) instrumentation.SetLogger(agent.logger) instrumentation.SetSourceRoot(sourceRoot) + instrumentation.SetRemoteConfiguration(agent.loadRemoteConfiguration()) if agent.setGlobalTracer || env.ScopeTracerGlobal.Value { opentracing.SetGlobalTracer(agent.Tracer()) } - cfg := agent.loadRemoteConfig() - _ = cfg - return agent, nil } diff --git a/agent/remote_config.go b/agent/remote_config.go index a23f8d19..d9184c1a 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -7,8 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/mitchellh/go-homedir" - "go.undefinedlabs.com/scopeagent/tags" "io/ioutil" "net/http" "net/url" @@ -16,47 +14,37 @@ import ( "path/filepath" "runtime" "time" + + "github.com/mitchellh/go-homedir" + + "go.undefinedlabs.com/scopeagent/tags" ) -func (a *Agent) loadRemoteConfig() map[string]interface{} { +func (a *Agent) loadRemoteConfiguration() map[string]interface{} { if a == nil || a.metadata == nil { return nil } - - var ( - path string - err error - configRequest = map[string]interface{}{} - ) + configRequest := map[string]interface{}{} addElementToMapIfEmpty(configRequest, tags.Repository, a.metadata[tags.Repository]) addElementToMapIfEmpty(configRequest, tags.Commit, a.metadata[tags.Commit]) + addElementToMapIfEmpty(configRequest, tags.Branch, a.metadata[tags.Branch]) addElementToMapIfEmpty(configRequest, tags.Service, a.metadata[tags.Service]) addElementToMapIfEmpty(configRequest, tags.Dependencies, a.metadata[tags.Dependencies]) - path, err = getLocalConfigurationPath(configRequest) - if err == nil { - file, lerr := os.Open(path) - err = lerr - if lerr == nil { - defer file.Close() - fileBytes, lerr := ioutil.ReadAll(file) - err = lerr - if lerr == nil { - var res map[string]interface{} - if lerr = json.Unmarshal(fileBytes, &res); lerr == nil { - return res - } else { - err = lerr - } - } + if cKeys, ok := a.metadata[tags.ConfigurationKeys]; ok { + cfgKeys := cKeys.([]string) + configRequest[tags.ConfigurationKeys] = cfgKeys + for _, item := range cfgKeys { + addElementToMapIfEmpty(configRequest, item, a.metadata[item]) } } - if err != nil { - a.logger.Printf("Error loading local configuration: %v", err) - } + return a.getOrSetRemoteConfigurationCache(configRequest, a.getRemoteConfiguration) +} + +func (a *Agent) getRemoteConfiguration(cfgRequest map[string]interface{}) map[string]interface{} { client := &http.Client{} curl := a.getUrl("api/agent/config") - payload, err := encodePayload(configRequest) + payload, err := encodePayload(cfgRequest) if err != nil { a.logger.Printf("Error encoding payload: %v", err) } @@ -138,20 +126,65 @@ func (a *Agent) loadRemoteConfig() map[string]interface{} { if statusCode != 0 && statusCode < 400 && lastError == nil { var resp map[string]interface{} if err := json.Unmarshal(bodyData, &resp); err == nil { - if path != "" { - if err := ioutil.WriteFile(path, bodyData, 0755); err != nil { - a.logger.Printf("Error writing json file: %v", err) - } - } return resp } else { - a.logger.Printf("Error unmarshalling msgpack: %v", err) + a.logger.Printf("Error unmarshalling json: %v", err) } } return nil } -func getLocalConfigurationPath(metadata map[string]interface{}) (string, error) { +func (a *Agent) getOrSetRemoteConfigurationCache(metadata map[string]interface{}, fn func(map[string]interface{}) map[string]interface{}) map[string]interface{} { + if metadata == nil { + return nil + } + var ( + path string + err error + ) + path, err = getRemoteConfigurationCachePath(metadata) + if err == nil { + // We try to load the cached version of the remote configuration + file, lerr := os.Open(path) + err = lerr + if lerr == nil { + defer file.Close() + fileBytes, lerr := ioutil.ReadAll(file) + err = lerr + if lerr == nil { + var res map[string]interface{} + if lerr = json.Unmarshal(fileBytes, &res); lerr == nil { + a.logger.Printf("Remote configuration cache: %v", file) + return res + } else { + err = lerr + } + } + } + } + if err != nil { + a.logger.Printf("Remote configuration cache: %v", err) + } + + if fn == nil { + return nil + } + + // Call the loader + resp := fn(metadata) + + if resp != nil && path != "" { + // Save a local cache for the response + if data, err := json.Marshal(&resp); err == nil { + if err := ioutil.WriteFile(path, data, 0755); err != nil { + a.logger.Printf("Error writing json file: %v", err) + } + } + } + return resp +} + +func getRemoteConfigurationCachePath(metadata map[string]interface{}) (string, error) { homeDir, err := homedir.Dir() if err != nil { return "", err diff --git a/instrumentation/testing/config/testing.go b/instrumentation/testing/config/testing.go index fb880815..dd7608b9 100644 --- a/instrumentation/testing/config/testing.go +++ b/instrumentation/testing/config/testing.go @@ -1,28 +1,34 @@ package config import ( + "fmt" + "go.undefinedlabs.com/scopeagent/instrumentation" "sync" ) var ( testsToSkip map[string]struct{} - m sync.RWMutex + m sync.Mutex ) -func SetFqnToSkip(fqns ...string) { +func GetCachedTestsMap() map[string]struct{} { m.Lock() defer m.Unlock() - testsToSkip = map[string]struct{}{} - for _, val := range fqns { - testsToSkip[val] = struct{}{} + if testsToSkip != nil { + return testsToSkip } -} - -func GetSkipMap() map[string]struct{} { - m.RLock() - defer m.RUnlock() + config := instrumentation.GetRemoteConfiguration() + testsToSkip = map[string]struct{}{} + if iCached, ok := config["cached"]; ok { + cachedTests := iCached.([]interface{}) + for _, item := range cachedTests { + testItem := item.(map[string]interface{}) + testFqn := fmt.Sprintf("%v.%v", testItem["test_suite"], testItem["test_name"]) + testsToSkip[testFqn] = struct{}{} + } + } return testsToSkip } diff --git a/instrumentation/testing/init.go b/instrumentation/testing/init.go index 120a7d0f..6016f2d9 100644 --- a/instrumentation/testing/init.go +++ b/instrumentation/testing/init.go @@ -31,7 +31,7 @@ func Init(m *testing.M) { tests = append(tests, testing.InternalTest{ Name: test.Name, F: func(t *testing.T) { // Creating a new test function as an indirection of the original test - if shouldSkipTest(t, funcPointer) { + if isTestCached(t, funcPointer) { return } addAutoInstrumentedTest(t) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 3453b5f0..a47b5b53 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -139,14 +139,14 @@ func (test *Test) Run(name string, f func(t *testing.T)) bool { pc, _, _, _ := runtime.Caller(1) if test.span == nil { // No span = not instrumented return test.t.Run(name, func(cT *testing.T) { - if shouldSkipTest(cT, pc) { + if isTestCached(cT, pc) { return } f(cT) }) } return test.t.Run(name, func(childT *testing.T) { - if shouldSkipTest(childT, pc) { + if isTestCached(childT, pc) { return } addAutoInstrumentedTest(childT) @@ -290,8 +290,8 @@ func addAutoInstrumentedTest(t *testing.T) { autoInstrumentedTests[t] = true } -// Should skip test -func shouldSkipTest(t *testing.T, pc uintptr) bool { +// Get if the test is cached +func isTestCached(t *testing.T, pc uintptr) bool { fullTestName := runner.GetOriginalTestName(t.Name()) testNameSlash := strings.IndexByte(fullTestName, '/') funcName := fullTestName @@ -307,8 +307,8 @@ func shouldSkipTest(t *testing.T, pc uintptr) bool { packageName := funcFullName[:funcNameIndex-1] fqn := fmt.Sprintf("%s.%s", packageName, fullTestName) - skipMap := config.GetSkipMap() - if _, ok := skipMap[fqn]; ok { + cachedMap := config.GetCachedTestsMap() + if _, ok := cachedMap[fqn]; ok { reflection.SkipAndFinishTest(t) return true } diff --git a/instrumentation/tracer.go b/instrumentation/tracer.go index ec610c3e..49af8be1 100644 --- a/instrumentation/tracer.go +++ b/instrumentation/tracer.go @@ -12,9 +12,10 @@ import ( ) var ( - tracer opentracing.Tracer = opentracing.NoopTracer{} - logger = log.New(ioutil.Discard, "", 0) - sourceRoot = "" + tracer opentracing.Tracer = opentracing.NoopTracer{} + logger = log.New(ioutil.Discard, "", 0) + sourceRoot = "" + remoteConfig = map[string]interface{}{} m sync.RWMutex ) @@ -61,6 +62,20 @@ func GetSourceRoot() string { return sourceRoot } +func SetRemoteConfiguration(config map[string]interface{}) { + m.Lock() + defer m.Unlock() + + remoteConfig = config +} + +func GetRemoteConfiguration() map[string]interface{} { + m.RLock() + defer m.RUnlock() + + return remoteConfig +} + //go:noinline func GetCallerInsideSourceRoot(skip int) (pc uintptr, file string, line int, ok bool) { pcs := make([]uintptr, 64) From f7f4317dbdc0210a51be549bc98263373221ea84 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 22:56:50 +0200 Subject: [PATCH 06/26] test CACHE status report --- instrumentation/testing/init.go | 3 --- instrumentation/testing/testing.go | 29 +++++++++++------------------ tags/tags.go | 7 ++++--- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/instrumentation/testing/init.go b/instrumentation/testing/init.go index 6016f2d9..58cda8e6 100644 --- a/instrumentation/testing/init.go +++ b/instrumentation/testing/init.go @@ -31,9 +31,6 @@ func Init(m *testing.M) { tests = append(tests, testing.InternalTest{ Name: test.Name, F: func(t *testing.T) { // Creating a new test function as an indirection of the original test - if isTestCached(t, funcPointer) { - return - } addAutoInstrumentedTest(t) tStruct := StartTestFromCaller(t, funcPointer) defer tStruct.end() diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index a47b5b53..7f236391 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -97,6 +97,15 @@ func StartTestFromCaller(t *testing.T, pc uintptr, opts ...Option) *Test { span, ctx := opentracing.StartSpanFromContextWithTracer(test.ctx, instrumentation.Tracer(), fullTestName, testTags) span.SetBaggageItem("trace.kind", "test") + + if isTestCached(t, pc) { + // Remove the Test struct from the hash map, so a call to Start while we end this instance will create a new struct + removeTest(t) + span.SetTag("test.status", tags.TestStatus_CACHE) + span.Finish() + t.SkipNow() + } + test.span = span test.ctx = ctx @@ -146,9 +155,6 @@ func (test *Test) Run(name string, f func(t *testing.T)) bool { }) } return test.t.Run(name, func(childT *testing.T) { - if isTestCached(childT, pc) { - return - } addAutoInstrumentedTest(childT) childTest := StartTestFromCaller(childT, pc) defer childTest.end() @@ -292,21 +298,8 @@ func addAutoInstrumentedTest(t *testing.T) { // Get if the test is cached func isTestCached(t *testing.T, pc uintptr) bool { - fullTestName := runner.GetOriginalTestName(t.Name()) - testNameSlash := strings.IndexByte(fullTestName, '/') - funcName := fullTestName - if testNameSlash >= 0 { - funcName = fullTestName[:testNameSlash] - } - - funcFullName := runtime.FuncForPC(pc).Name() - funcNameIndex := strings.LastIndex(funcFullName, funcName) - if funcNameIndex < 1 { - funcNameIndex = len(funcFullName) - } - packageName := funcFullName[:funcNameIndex-1] - - fqn := fmt.Sprintf("%s.%s", packageName, fullTestName) + pkgName, testName := getPackageAndName(pc) + fqn := fmt.Sprintf("%s.%s", pkgName, testName) cachedMap := config.GetCachedTestsMap() if _, ok := cachedMap[fqn]; ok { reflection.SkipAndFinishTest(t) diff --git a/tags/tags.go b/tags/tags.go index 9f880e00..fbda0bad 100644 --- a/tags/tags.go +++ b/tags/tags.go @@ -48,9 +48,10 @@ const ( LogLevel_DEBUG = "DEBUG" LogLevel_VERBOSE = "VERBOSE" - TestStatus_FAIL = "FAIL" - TestStatus_PASS = "PASS" - TestStatus_SKIP = "SKIP" + TestStatus_FAIL = "FAIL" + TestStatus_PASS = "PASS" + TestStatus_SKIP = "SKIP" + TestStatus_CACHE = "CACHE" TestingMode = "testing" From 4c38cd2bc2fde8d2356e342dce05ec3cfc23d568 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 23:07:17 +0200 Subject: [PATCH 07/26] log cached info --- instrumentation/testing/testing.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 7f236391..5bce92e0 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -302,8 +302,10 @@ func isTestCached(t *testing.T, pc uintptr) bool { fqn := fmt.Sprintf("%s.%s", pkgName, testName) cachedMap := config.GetCachedTestsMap() if _, ok := cachedMap[fqn]; ok { + instrumentation.Logger().Printf("Test '%v' is cached.", fqn) reflection.SkipAndFinishTest(t) return true } + instrumentation.Logger().Printf("Test '%v' is not cached.", fqn) return false } From 3941ed0fdb1434e71424489ee49e42347e46f1db Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 23:11:41 +0200 Subject: [PATCH 08/26] verbose cache log to stdout --- instrumentation/testing/testing.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 5bce92e0..626b7b10 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -303,9 +303,11 @@ func isTestCached(t *testing.T, pc uintptr) bool { cachedMap := config.GetCachedTestsMap() if _, ok := cachedMap[fqn]; ok { instrumentation.Logger().Printf("Test '%v' is cached.", fqn) + fmt.Printf("Test '%v' is cached.", fqn) reflection.SkipAndFinishTest(t) return true } instrumentation.Logger().Printf("Test '%v' is not cached.", fqn) + fmt.Printf("Test '%v' is not cached.", fqn) return false } From 0a54bf406f5ed57643f06c87776a5b555441635d Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 23:21:37 +0200 Subject: [PATCH 09/26] github action change --- .github/workflows/go.yml | 11 +++++++++++ instrumentation/testing/testing.go | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 091f2810..961c5ae9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -21,10 +21,21 @@ jobs: - name: Get dependencies run: go get -v -t -d ./... + - name: Create log folder + run: mkdir /home/runner/.scope-results + - name: Test run: go test -v -race -covermode=atomic ./... env: SCOPE_DSN: ${{ secrets.SCOPE_DSN }} + SCOPE_LOGGER_ROOT: /home/runner/.scope-results + + - name: Upload Scope logs + if: always() + uses: actions/upload-artifact@v1 + with: + name: Scope for Go logs + path: /home/runner/.scope-results fossa: name: FOSSA diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 626b7b10..2e52d80a 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -303,7 +303,7 @@ func isTestCached(t *testing.T, pc uintptr) bool { cachedMap := config.GetCachedTestsMap() if _, ok := cachedMap[fqn]; ok { instrumentation.Logger().Printf("Test '%v' is cached.", fqn) - fmt.Printf("Test '%v' is cached.", fqn) + fmt.Print("--- CACHED ") reflection.SkipAndFinishTest(t) return true } From 6492ab8521e49e60b47ebcdf93953194ea067843 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 23:28:56 +0200 Subject: [PATCH 10/26] debug log info --- .github/workflows/go.yml | 1 + agent/remote_config.go | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 961c5ae9..acd8efbb 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -29,6 +29,7 @@ jobs: env: SCOPE_DSN: ${{ secrets.SCOPE_DSN }} SCOPE_LOGGER_ROOT: /home/runner/.scope-results + SCOPE_DEBUG: true - name: Upload Scope logs if: always() diff --git a/agent/remote_config.go b/agent/remote_config.go index d9184c1a..83447ff0 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -154,7 +154,11 @@ func (a *Agent) getOrSetRemoteConfigurationCache(metadata map[string]interface{} if lerr == nil { var res map[string]interface{} if lerr = json.Unmarshal(fileBytes, &res); lerr == nil { - a.logger.Printf("Remote configuration cache: %v", file) + if a.debugMode { + a.logger.Printf("Remote configuration cache: %v", string(fileBytes)) + } else { + a.logger.Printf("Remote configuration cache: %v", path) + } return res } else { err = lerr @@ -176,6 +180,9 @@ func (a *Agent) getOrSetRemoteConfigurationCache(metadata map[string]interface{} if resp != nil && path != "" { // Save a local cache for the response if data, err := json.Marshal(&resp); err == nil { + if a.debugMode { + a.logger.Printf("Saving Remote configuration cache: %v", string(data)) + } if err := ioutil.WriteFile(path, data, 0755); err != nil { a.logger.Printf("Error writing json file: %v", err) } From 0b1f2ef4b841c34d136b56924e2a67cfd29733c4 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Tue, 28 Apr 2020 23:37:15 +0200 Subject: [PATCH 11/26] debug data --- agent/remote_config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agent/remote_config.go b/agent/remote_config.go index 83447ff0..deef87ad 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -37,7 +37,9 @@ func (a *Agent) loadRemoteConfiguration() map[string]interface{} { addElementToMapIfEmpty(configRequest, item, a.metadata[item]) } } - + if a.debugMode { + a.logger.Printf("Getting remote configuration for: %v", configRequest) + } return a.getOrSetRemoteConfigurationCache(configRequest, a.getRemoteConfiguration) } From faaeb925982af6decccf1c6cb346153f4e603d1f Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 14:53:17 +0200 Subject: [PATCH 12/26] Capabilities in metadata --- agent/agent.go | 7 +++++++ tags/tags.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/agent/agent.go b/agent/agent.go index 9953b1eb..14c10cf5 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -321,6 +321,13 @@ func NewAgent(options ...Option) (*Agent, error) { } agent.metadata[tags.SourceRoot] = sourceRoot + // Capabilities + agent.metadata[tags.Capabilities] = map[string]interface{}{ + tags.Capabilities_CodePath: testing.CoverMode(), + tags.Capabilities_RunnerCache: true, + tags.Capabilities_RunnerRetries: agent.failRetriesCount > 0, + } + if !agent.testingMode { if env.ScopeTestingMode.IsSet { agent.testingMode = env.ScopeTestingMode.Value diff --git a/tags/tags.go b/tags/tags.go index fbda0bad..bc071578 100644 --- a/tags/tags.go +++ b/tags/tags.go @@ -22,6 +22,12 @@ const ( SourceRoot = "source.root" Diff = "diff" + Capabilities = "capabilities" + Capabilities_CodePath = "code.path" + Capabilities_ProcessEnd = "process.end" + Capabilities_RunnerRetries = "runner.retries" + Capabilities_RunnerCache = "runner.cache" + CI = "ci.in_ci" CIProvider = "ci.provider" CIBuildId = "ci.build_id" From e3d6b6144b3eff42d68ddb6b58823f017aaaa45f Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 15:03:56 +0200 Subject: [PATCH 13/26] capability fixes --- agent/agent.go | 2 +- agent/remote_config.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 14c10cf5..facb9f62 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -323,7 +323,7 @@ func NewAgent(options ...Option) (*Agent, error) { // Capabilities agent.metadata[tags.Capabilities] = map[string]interface{}{ - tags.Capabilities_CodePath: testing.CoverMode(), + tags.Capabilities_CodePath: testing.CoverMode() != "", tags.Capabilities_RunnerCache: true, tags.Capabilities_RunnerRetries: agent.failRetriesCount > 0, } diff --git a/agent/remote_config.go b/agent/remote_config.go index deef87ad..ccd843e9 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -38,7 +38,8 @@ func (a *Agent) loadRemoteConfiguration() map[string]interface{} { } } if a.debugMode { - a.logger.Printf("Getting remote configuration for: %v", configRequest) + jsBytes, _ := json.Marshal(configRequest) + a.logger.Printf("Getting remote configuration for: %v", string(jsBytes)) } return a.getOrSetRemoteConfigurationCache(configRequest, a.getRemoteConfiguration) } From af1c2294f5bd3bf51670a2858e6a5555faf31dbe Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 16:07:55 +0200 Subject: [PATCH 14/26] refactoring --- agent/recorder.go | 24 +----------------------- agent/remote_config.go | 2 +- agent/util.go | 27 ++++++++++++++++++++++++++- instrumentation/testing/testing.go | 4 ++-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/agent/recorder.go b/agent/recorder.go index 400e085a..25b3790b 100644 --- a/agent/recorder.go +++ b/agent/recorder.go @@ -2,7 +2,6 @@ package agent import ( "bytes" - "compress/gzip" "crypto/x509" "errors" "fmt" @@ -15,7 +14,6 @@ import ( "time" "github.com/google/uuid" - "github.com/vmihailenco/msgpack" "gopkg.in/tomb.v2" "go.undefinedlabs.com/scopeagent/tags" @@ -154,7 +152,7 @@ func (r *SpanRecorder) sendSpans() (error, bool) { "events": events, tags.AgentID: r.agentId, } - buf, err := encodePayload(payload) + buf, err := msgPackEncodePayload(payload) if err != nil { atomic.AddInt64(&r.stats.sendSpansKo, 1) atomic.AddInt64(&r.stats.spansNotSent, int64(len(spans))) @@ -353,26 +351,6 @@ func (r *SpanRecorder) getPayloadComponents(span tracer.RawSpan) (PayloadSpan, [ return payloadSpan, events } -// Encodes `payload` using msgpack and compress it with gzip -func encodePayload(payload map[string]interface{}) (*bytes.Buffer, error) { - binaryPayload, err := msgpack.Marshal(payload) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - _, err = zw.Write(binaryPayload) - if err != nil { - return nil, err - } - if err := zw.Close(); err != nil { - return nil, err - } - - return &buf, nil -} - // Gets the current flush frequency func (r *SpanRecorder) getFlushFrequency() time.Duration { r.RLock() diff --git a/agent/remote_config.go b/agent/remote_config.go index ccd843e9..b1e92160 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -47,7 +47,7 @@ func (a *Agent) loadRemoteConfiguration() map[string]interface{} { func (a *Agent) getRemoteConfiguration(cfgRequest map[string]interface{}) map[string]interface{} { client := &http.Client{} curl := a.getUrl("api/agent/config") - payload, err := encodePayload(cfgRequest) + payload, err := msgPackEncodePayload(cfgRequest) if err != nil { a.logger.Printf("Error encoding payload: %v", err) } diff --git a/agent/util.go b/agent/util.go index 9fbca97f..d1087b9d 100644 --- a/agent/util.go +++ b/agent/util.go @@ -1,6 +1,11 @@ package agent -import "os" +import ( + "bytes" + "compress/gzip" + "github.com/vmihailenco/msgpack" + "os" +) func addToMapIfEmpty(dest map[string]interface{}, source map[string]interface{}) { if source == nil { @@ -28,3 +33,23 @@ func getSourceRootFromEnv(key string) string { } return "" } + +// Encodes `payload` using msgpack and compress it with gzip +func msgPackEncodePayload(payload map[string]interface{}) (*bytes.Buffer, error) { + binaryPayload, err := msgpack.Marshal(payload) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + _, err = zw.Write(binaryPayload) + if err != nil { + return nil, err + } + if err := zw.Close(); err != nil { + return nil, err + } + + return &buf, nil +} diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 2e52d80a..51914ba4 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -99,10 +99,10 @@ func StartTestFromCaller(t *testing.T, pc uintptr, opts ...Option) *Test { span.SetBaggageItem("trace.kind", "test") if isTestCached(t, pc) { - // Remove the Test struct from the hash map, so a call to Start while we end this instance will create a new struct - removeTest(t) span.SetTag("test.status", tags.TestStatus_CACHE) span.Finish() + // Remove the Test struct from the hash map, so a call to Start while we end this instance will create a new struct + removeTest(t) t.SkipNow() } From 5f43f344b433355c7c6981ba1f590b3ae69cf693 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 16:32:48 +0200 Subject: [PATCH 15/26] sort dependencies values --- agent/dependencies.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/dependencies.go b/agent/dependencies.go index b0262e34..2047b826 100644 --- a/agent/dependencies.go +++ b/agent/dependencies.go @@ -3,6 +3,7 @@ package agent import ( "os/exec" "regexp" + "sort" "strings" ) @@ -24,6 +25,7 @@ func getDependencyMap() map[string]string { } dependencies := map[string]string{} for k, v := range deps { + sort.Strings(v) dependencies[k] = strings.Join(v, ", ") } return dependencies From 41d82ebb5d39a9e8b8c893638a2add29367b3501 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 16:38:05 +0200 Subject: [PATCH 16/26] readme update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a624f7c7..0430c502 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [Scope](https://scope.dev) agent for Go - ## Installation instructions Check [https://docs.scope.dev/docs/go-installation](https://docs.scope.dev/docs/go-installation) for detailed installation and usage instructions. From f7b2b9478f46fb5f1ecf1cf388510f07ee01ac9d Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 16:45:08 +0200 Subject: [PATCH 17/26] TestHttpServer change --- instrumentation/nethttp/nethttp_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/nethttp/nethttp_test.go b/instrumentation/nethttp/nethttp_test.go index 1efab075..a919de9b 100644 --- a/instrumentation/nethttp/nethttp_test.go +++ b/instrumentation/nethttp/nethttp_test.go @@ -87,6 +87,7 @@ func TestHttpServer(t *testing.T) { if len(spans) != 2 { t.Fatalf("there aren't the right number of spans: %d", len(spans)) } + checkTags(t, spans[0].Tags, map[string]string{ "component": "net/http", "http.method": "POST", From bb3fc1b9597c6e914695f345a6f19c749897fc56 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 16:50:41 +0200 Subject: [PATCH 18/26] remove stdout message --- instrumentation/testing/testing.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 51914ba4..3012fec9 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -303,11 +303,10 @@ func isTestCached(t *testing.T, pc uintptr) bool { cachedMap := config.GetCachedTestsMap() if _, ok := cachedMap[fqn]; ok { instrumentation.Logger().Printf("Test '%v' is cached.", fqn) - fmt.Print("--- CACHED ") + fmt.Print("[SCOPE CACHED] ") reflection.SkipAndFinishTest(t) return true } instrumentation.Logger().Printf("Test '%v' is not cached.", fqn) - fmt.Printf("Test '%v' is not cached.", fqn) return false } From 9e8518f155ed7787eb78ce47514ae2c0a6ca3c9e Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 17:11:05 +0200 Subject: [PATCH 19/26] func comments --- agent/remote_config.go | 4 ++++ instrumentation/testing/config/testing.go | 1 + 2 files changed, 5 insertions(+) diff --git a/agent/remote_config.go b/agent/remote_config.go index b1e92160..18250af2 100644 --- a/agent/remote_config.go +++ b/agent/remote_config.go @@ -20,6 +20,7 @@ import ( "go.undefinedlabs.com/scopeagent/tags" ) +// Loads the remote agent configuration from local cache, if not exists then retrieve it from the server func (a *Agent) loadRemoteConfiguration() map[string]interface{} { if a == nil || a.metadata == nil { return nil @@ -44,6 +45,7 @@ func (a *Agent) loadRemoteConfiguration() map[string]interface{} { return a.getOrSetRemoteConfigurationCache(configRequest, a.getRemoteConfiguration) } +// Gets the remote agent configuration from the endpoint + api/agent/config func (a *Agent) getRemoteConfiguration(cfgRequest map[string]interface{}) map[string]interface{} { client := &http.Client{} curl := a.getUrl("api/agent/config") @@ -137,6 +139,7 @@ func (a *Agent) getRemoteConfiguration(cfgRequest map[string]interface{}) map[st return nil } +// Gets or sets the remote agent configuration local cache func (a *Agent) getOrSetRemoteConfigurationCache(metadata map[string]interface{}, fn func(map[string]interface{}) map[string]interface{}) map[string]interface{} { if metadata == nil { return nil @@ -194,6 +197,7 @@ func (a *Agent) getOrSetRemoteConfigurationCache(metadata map[string]interface{} return resp } +// Gets the remote agent configuration local cache path func getRemoteConfigurationCachePath(metadata map[string]interface{}) (string, error) { homeDir, err := homedir.Dir() if err != nil { diff --git a/instrumentation/testing/config/testing.go b/instrumentation/testing/config/testing.go index dd7608b9..517424df 100644 --- a/instrumentation/testing/config/testing.go +++ b/instrumentation/testing/config/testing.go @@ -12,6 +12,7 @@ var ( m sync.Mutex ) +// Gets the map of cached tests func GetCachedTestsMap() map[string]struct{} { m.Lock() defer m.Unlock() From 3690df74895882175ae259d7b89ef26cf1e59636 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 17:16:13 +0200 Subject: [PATCH 20/26] code cleanup and comments --- instrumentation/nethttp/nethttp_test.go | 1 - instrumentation/testing/config/testing.go | 14 ++++++++------ reflection/reflect.go | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/instrumentation/nethttp/nethttp_test.go b/instrumentation/nethttp/nethttp_test.go index a919de9b..1efab075 100644 --- a/instrumentation/nethttp/nethttp_test.go +++ b/instrumentation/nethttp/nethttp_test.go @@ -87,7 +87,6 @@ func TestHttpServer(t *testing.T) { if len(spans) != 2 { t.Fatalf("there aren't the right number of spans: %d", len(spans)) } - checkTags(t, spans[0].Tags, map[string]string{ "component": "net/http", "http.method": "POST", diff --git a/instrumentation/testing/config/testing.go b/instrumentation/testing/config/testing.go index 517424df..46e974b7 100644 --- a/instrumentation/testing/config/testing.go +++ b/instrumentation/testing/config/testing.go @@ -23,12 +23,14 @@ func GetCachedTestsMap() map[string]struct{} { config := instrumentation.GetRemoteConfiguration() testsToSkip = map[string]struct{}{} - if iCached, ok := config["cached"]; ok { - cachedTests := iCached.([]interface{}) - for _, item := range cachedTests { - testItem := item.(map[string]interface{}) - testFqn := fmt.Sprintf("%v.%v", testItem["test_suite"], testItem["test_name"]) - testsToSkip[testFqn] = struct{}{} + if config != nil { + if iCached, ok := config["cached"]; ok { + cachedTests := iCached.([]interface{}) + for _, item := range cachedTests { + testItem := item.(map[string]interface{}) + testFqn := fmt.Sprintf("%v.%v", testItem["test_suite"], testItem["test_name"]) + testsToSkip[testFqn] = struct{}{} + } } } return testsToSkip diff --git a/reflection/reflect.go b/reflection/reflect.go index 40cdbf58..93c2c237 100644 --- a/reflection/reflect.go +++ b/reflection/reflect.go @@ -150,6 +150,7 @@ func GetBenchmarkResult(b *testing.B) (*testing.BenchmarkResult, error) { } } +// Mark the current test as skipped and finished without exit the current goroutine func SkipAndFinishTest(t *testing.T) { mu := GetTestMutex(t) if mu != nil { From 7630e7e908001032896eb7e0a18356d9ac1dccd8 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 17:21:30 +0200 Subject: [PATCH 21/26] restore readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0430c502..a624f7c7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [Scope](https://scope.dev) agent for Go + ## Installation instructions Check [https://docs.scope.dev/docs/go-installation](https://docs.scope.dev/docs/go-installation) for detailed installation and usage instructions. From a6a3eb6adbbd19037de2db637c471d4604e1130c Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 17:24:40 +0200 Subject: [PATCH 22/26] remove skip test on not instrumented tests --- instrumentation/testing/testing.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/instrumentation/testing/testing.go b/instrumentation/testing/testing.go index 3012fec9..5903a39f 100644 --- a/instrumentation/testing/testing.go +++ b/instrumentation/testing/testing.go @@ -145,15 +145,10 @@ func (test *Test) Context() context.Context { // Runs an auto instrumented sub test func (test *Test) Run(name string, f func(t *testing.T)) bool { - pc, _, _, _ := runtime.Caller(1) if test.span == nil { // No span = not instrumented - return test.t.Run(name, func(cT *testing.T) { - if isTestCached(cT, pc) { - return - } - f(cT) - }) + return test.t.Run(name, f) } + pc, _, _, _ := runtime.Caller(1) return test.t.Run(name, func(childT *testing.T) { addAutoInstrumentedTest(childT) childTest := StartTestFromCaller(childT, pc) From 4d6134fee6bc223a5c13c5a6d03cfb846f9bf2ee Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 19:15:20 +0200 Subject: [PATCH 23/26] Configuration algorithm --- agent/agent.go | 27 ++++++++++++++++++++++++++- agent/util.go | 13 +++++++++++++ env/vars.go | 3 +++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index facb9f62..06b390fc 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -368,7 +368,32 @@ func NewAgent(options ...Option) (*Agent, error) { instrumentation.SetTracer(agent.tracer) instrumentation.SetLogger(agent.logger) instrumentation.SetSourceRoot(sourceRoot) - instrumentation.SetRemoteConfiguration(agent.loadRemoteConfiguration()) + enableRemoteConfig := false + if env.ScopeRunnerEnabled.Value { + // runner is enabled + if env.ScopeRunnerIncludeBranches.Value == nil && env.ScopeRunnerExcludeBranches.Value == nil { + // both include and exclude branches are not defined + enableRemoteConfig = true + } else if iBranch, ok := agent.metadata[tags.Branch]; ok { + branch := iBranch.(string) + included := sliceContains(env.ScopeRunnerIncludeBranches.Value, branch) + excluded := sliceContains(env.ScopeRunnerExcludeBranches.Value, branch) + enableRemoteConfig = included // By default we use the value inside the include slice + if env.ScopeRunnerExcludeBranches.Value != nil { + if included && excluded { + // If appears in both slices, write in the logger and disable the runner configuration + agent.logger.Printf("The branch '%v' appears in both included and excluded branches. The branch will be excluded.", branch) + enableRemoteConfig = false + } else { + // We enable the remote config if is include or not excluded + enableRemoteConfig = included || !excluded + } + } + } + } + if enableRemoteConfig { + instrumentation.SetRemoteConfiguration(agent.loadRemoteConfiguration()) + } if agent.setGlobalTracer || env.ScopeTracerGlobal.Value { opentracing.SetGlobalTracer(agent.Tracer()) } diff --git a/agent/util.go b/agent/util.go index d1087b9d..2e34838f 100644 --- a/agent/util.go +++ b/agent/util.go @@ -53,3 +53,16 @@ func msgPackEncodePayload(payload map[string]interface{}) (*bytes.Buffer, error) return &buf, nil } + +// Gets if the slice contains an item +func sliceContains(slice []string, item string) bool { + if slice == nil { + return false + } + for _, a := range slice { + if a == item { + return true + } + } + return false +} diff --git a/env/vars.go b/env/vars.go index bd726a20..873f923f 100644 --- a/env/vars.go +++ b/env/vars.go @@ -23,4 +23,7 @@ var ( ScopeInstrumentationHttpStacktrace = newBooleanEnvVar(false, "SCOPE_INSTRUMENTATION_HTTP_STACKTRACE") ScopeInstrumentationDbStatementValues = newBooleanEnvVar(false, "SCOPE_INSTRUMENTATION_DB_STATEMENT_VALUES") ScopeInstrumentationDbStacktrace = newBooleanEnvVar(false, "SCOPE_INSTRUMENTATION_DB_STACKTRACE") + ScopeRunnerEnabled = newBooleanEnvVar(false, "SCOPE_RUNNER_ENABLED") + ScopeRunnerIncludeBranches = newSliceEnvVar(nil, "SCOPE_RUNNER_INCLUDE_BRANCHES") + ScopeRunnerExcludeBranches = newSliceEnvVar(nil, "SCOPE_RUNNER_EXCLUDE_BRANCHES") ) From e64c24ed1a3fd5a0a4f3942befdcb5640f95fee1 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Wed, 29 Apr 2020 19:19:33 +0200 Subject: [PATCH 24/26] Capability runner cache --- .github/workflows/go.yml | 2 ++ agent/agent.go | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index acd8efbb..70798293 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,6 +30,8 @@ jobs: SCOPE_DSN: ${{ secrets.SCOPE_DSN }} SCOPE_LOGGER_ROOT: /home/runner/.scope-results SCOPE_DEBUG: true + SCOPE_RUNNER_ENABLED: true + SCOPE_RUNNER_EXCLUDE_BRANCHES: master - name: Upload Scope logs if: always() diff --git a/agent/agent.go b/agent/agent.go index 06b390fc..fb1f88ad 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -322,11 +322,11 @@ func NewAgent(options ...Option) (*Agent, error) { agent.metadata[tags.SourceRoot] = sourceRoot // Capabilities - agent.metadata[tags.Capabilities] = map[string]interface{}{ + capabilities := map[string]interface{}{ tags.Capabilities_CodePath: testing.CoverMode() != "", - tags.Capabilities_RunnerCache: true, tags.Capabilities_RunnerRetries: agent.failRetriesCount > 0, } + agent.metadata[tags.Capabilities] = capabilities if !agent.testingMode { if env.ScopeTestingMode.IsSet { @@ -391,6 +391,7 @@ func NewAgent(options ...Option) (*Agent, error) { } } } + capabilities[tags.Capabilities_RunnerCache] = enableRemoteConfig if enableRemoteConfig { instrumentation.SetRemoteConfiguration(agent.loadRemoteConfiguration()) } From daca6615647e3a7edb0a45f0db18414ed50cc758 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Thu, 30 Apr 2020 00:08:49 +0200 Subject: [PATCH 25/26] Readme update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a624f7c7..0430c502 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [Scope](https://scope.dev) agent for Go - ## Installation instructions Check [https://docs.scope.dev/docs/go-installation](https://docs.scope.dev/docs/go-installation) for detailed installation and usage instructions. From b31e6f26412dd4dd67c93a4aa1f5b49a8a5f5ce4 Mon Sep 17 00:00:00 2001 From: Daniel Redondo Date: Thu, 30 Apr 2020 15:17:24 +0200 Subject: [PATCH 26/26] capabilities changes --- agent/agent.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/agent.go b/agent/agent.go index fb1f88ad..23d18dd9 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -324,6 +324,7 @@ func NewAgent(options ...Option) (*Agent, error) { // Capabilities capabilities := map[string]interface{}{ tags.Capabilities_CodePath: testing.CoverMode() != "", + tags.Capabilities_RunnerCache: false, tags.Capabilities_RunnerRetries: agent.failRetriesCount > 0, } agent.metadata[tags.Capabilities] = capabilities @@ -371,6 +372,7 @@ func NewAgent(options ...Option) (*Agent, error) { enableRemoteConfig := false if env.ScopeRunnerEnabled.Value { // runner is enabled + capabilities[tags.Capabilities_RunnerCache] = true if env.ScopeRunnerIncludeBranches.Value == nil && env.ScopeRunnerExcludeBranches.Value == nil { // both include and exclude branches are not defined enableRemoteConfig = true @@ -391,7 +393,6 @@ func NewAgent(options ...Option) (*Agent, error) { } } } - capabilities[tags.Capabilities_RunnerCache] = enableRemoteConfig if enableRemoteConfig { instrumentation.SetRemoteConfiguration(agent.loadRemoteConfiguration()) }