Repository: VictoriaMetrics/VictoriaMetrics
Pull request #4345 (2023-05-25 13:15:52)

app/vmctl: fix vm-native tests
In the import handler, we are using `stream.Parse` function. This function runs callback in the goroutines, so we have a situation when two goroutines write to the same slice simultaneously. To prevent this situation, I added mutex. 
To check that all tests work well, I have run this script

```bash
for i in {1..1000}; do go test -run Test_vmNativeProcessor_run || echo TESTS WAS FAILED; done
```

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4345?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Patch coverage has no change and project coverage change: **`+0.01`** :tada:
> Comparison is base [(`76f7e66`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/76f7e66d8e7d0bb05b3577466227651ce8d779ff?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 61.20% compared to head [(`31a2e9c`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4345?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) 61.22%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4345      +/-   ##
==========================================
+ Coverage   61.20%   61.22%   +0.01%     
==========================================
  Files         330      330              
  Lines       58997    58997              
==========================================
+ Hits        36112    36118       +6     
+ Misses      20914    20912       -2     
+ Partials     1971     1967       -4     
```


[see 6 files with indirect coverage changes](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4345/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/4345?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).


Patch:
diff --git a/app/vmalert/manager.go b/app/vmalert/manager.go
index fa04f778a..25fdfbe92 100644
--- a/app/vmalert/manager.go
+++ b/app/vmalert/manager.go
@@ -188,6 +188,7 @@ func (g *Group) toAPI() APIGroup {
 
 		Labels: g.Labels,
 	}
+	ag.Rules = make([]APIRule, 0)
 	for _, r := range g.Rules {
 		ag.Rules = append(ag.Rules, r.ToAPI())
 	}


Test patch:
diff --git a/app/vmalert/web_test.go b/app/vmalert/web_test.go
index 3a70f6fef..a036a586f 100644
--- a/app/vmalert/web_test.go
+++ b/app/vmalert/web_test.go
@@ -165,8 +165,8 @@ func TestHandler(t *testing.T) {
 }
 
 func TestEmptyResponse(t *testing.T) {
-	rh := &requestHandler{m: &manager{groups: make(map[uint64]*Group)}}
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }))
+	rhWithNoGroups := &requestHandler{m: &manager{groups: make(map[uint64]*Group)}}
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rhWithNoGroups.handler(w, r) }))
 	defer ts.Close()
 
 	getResp := func(url string, to interface{}, code int) {
@@ -190,7 +190,7 @@ func TestEmptyResponse(t *testing.T) {
 		}
 	}
 
-	t.Run("/api/v1/alerts", func(t *testing.T) {
+	t.Run("no groups /api/v1/alerts", func(t *testing.T) {
 		lr := listAlertsResponse{}
 		getResp(ts.URL+"/api/v1/alerts", &lr, 200)
 		if lr.Data.Alerts == nil {
@@ -204,7 +204,7 @@ func TestEmptyResponse(t *testing.T) {
 		}
 	})
 
-	t.Run("/api/v1/rules", func(t *testing.T) {
+	t.Run("no groups /api/v1/rules", func(t *testing.T) {
 		lr := listGroupsResponse{}
 		getResp(ts.URL+"/api/v1/rules", &lr, 200)
 		if lr.Data.Groups == nil {
@@ -217,4 +217,26 @@ func TestEmptyResponse(t *testing.T) {
 			t.Errorf("expected /api/v1/rules response to have non-nil data")
 		}
 	})
+
+	rhWithEmptyGroup := &requestHandler{m: &manager{groups: map[uint64]*Group{0: {Name: "test"}}}}
+	ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rhWithEmptyGroup.handler(w, r) })
+
+	t.Run("empty group /api/v1/rules", func(t *testing.T) {
+		lr := listGroupsResponse{}
+		getResp(ts.URL+"/api/v1/rules", &lr, 200)
+		if lr.Data.Groups == nil {
+			t.Fatalf("expected /api/v1/rules response to have non-nil data")
+		}
+
+		lr = listGroupsResponse{}
+		getResp(ts.URL+"/vmalert/api/v1/rules", &lr, 200)
+		if lr.Data.Groups == nil {
+			t.Fatalf("expected /api/v1/rules response to have non-nil data")
+		}
+
+		group := lr.Data.Groups[0]
+		if group.Rules == nil {
+			t.Fatalf("expected /api/v1/rules response to have non-nil rules for group")
+		}
+	})
 }
diff --git a/app/vmctl/testdata/servers_integration_test/remote_write_server.go b/app/vmctl/testdata/servers_integration_test/remote_write_server.go
index cac2dfca0..ece2bde21 100644
--- a/app/vmctl/testdata/servers_integration_test/remote_write_server.go
+++ b/app/vmctl/testdata/servers_integration_test/remote_write_server.go
@@ -2,6 +2,7 @@ package remote_read_integration
 
 import (
 	"bufio"
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"log"
@@ -10,6 +11,7 @@ import (
 	"reflect"
 	"sort"
 	"strconv"
+	"sync"
 	"testing"
 	"time"
 
@@ -169,14 +171,21 @@ func (rws *RemoteWriteServer) valuesHandler() http.Handler {
 			Data:   metricNames,
 		}
 
-		err := json.NewEncoder(w).Encode(resp)
+		buf := bytes.NewBuffer(nil)
+		err := json.NewEncoder(buf).Encode(resp)
 		if err != nil {
 			log.Printf("error send series: %s", err)
 			w.WriteHeader(http.StatusInternalServerError)
 			return
 		}
 
-		w.WriteHeader(http.StatusNoContent)
+		w.WriteHeader(http.StatusOK)
+		_, err = w.Write(buf.Bytes())
+		if err != nil {
+			log.Printf("error send series: %s", err)
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
 		return
 	})
 }
@@ -191,7 +200,6 @@ func (rws *RemoteWriteServer) exportNativeHandler() http.Handler {
 			return
 		}
 
-		w.WriteHeader(http.StatusNoContent)
 		return
 	})
 }
@@ -202,6 +210,7 @@ func (rws *RemoteWriteServer) importNativeHandler(t *testing.T) http.Handler {
 		defer common.StopUnmarshalWorkers()
 
 		var gotTimeSeries []vm.TimeSeries
+		var mx sync.RWMutex
 
 		err := stream.Parse(r.Body, false, func(block *stream.Block) error {
 			mn := &block.MetricName
@@ -218,7 +227,9 @@ func (rws *RemoteWriteServer) importNativeHandler(t *testing.T) http.Handler {
 				})
 			}
 
+			mx.Lock()
 			gotTimeSeries = append(gotTimeSeries, timeseries)
+			mx.Unlock()
 
 			return nil
 		})
@@ -244,7 +255,8 @@ func (rws *RemoteWriteServer) importNativeHandler(t *testing.T) http.Handler {
 
 		if !reflect.DeepEqual(gotTimeSeries, rws.expectedSeries) {
 			w.WriteHeader(http.StatusInternalServerError)
-			t.Fatalf("datasets not equal, expected: %#v;\n got: %#v", rws.expectedSeries, gotTimeSeries)
+			t.Errorf("datasets not equal, expected: %#v;\n got: %#v", rws.expectedSeries, gotTimeSeries)
+			return
 		}
 
 		w.WriteHeader(http.StatusNoContent)