ronantakizawa/codereview-bench huggingface dataset:
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:
	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", err
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", err
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d", s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}

	return sil.Id, nil
}

// canUpdate returns true if silence a can be updated to b without
// affecting the historic view of silencing.
func canUpdate(a, b *pb.Silence, now time.Time) bool {
	if !reflect.DeepEqual(a.Matchers, b.Matchers) {
		return false

Review comment (suggestion):
```suggestion
			return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(sils), s.limits.MaxSilences)
```


Code after:
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(sils), s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}

	return sil.Id, nil
}

// canUpdate returns true if silence a can be updated to b without
// affecting the historic view of silencing.
func canUpdate(a, b *pb.Silence, now time.Time) bool {
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:
		return "", ErrNotFound
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", err
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", err
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d", s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}

	return sil.Id, nil
}

// canUpdate returns true if silence a can be updated to b without
// affecting the historic view of silencing.

Review comment (suggestion):
```suggestion
			return "", fmt.Errorf("unable to query silences while calculating the limit: %w", err)
```

Code after:
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return "", ErrNotFound
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(sils), s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}

	return sil.Id, nil
}
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return "", ErrNotFound
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", err
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", err
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d", s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}

	return sil.Id, nil

Review comment (suggestion):
```suggestion
			return "", fmt.Errorf("unable to query the silences while calculating the limit: %w", err)
```

Code after:
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return "", ErrNotFound
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)
		}
		if getState(prev, s.nowUTC()) != types.SilenceStateExpired {
			// We cannot update the silence, expire the old one.
			if err := s.expire(prev.Id); err != nil {
				return "", fmt.Errorf("expire previous silence: %w", err)
			}
		}
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences > 0 {
		// Get the number of active and pending silences to enforce limits.
		q := &query{}
		err := QState(types.SilenceStateActive, types.SilenceStatePending)(q)
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		sils, _, err := s.query(q, s.nowUTC())
		if err != nil {
			return "", fmt.Errorf("unable to query silences while checking limits: %w", err)
		}
		if len(sils)+1 > s.limits.MaxSilences {
			return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(sils), s.limits.MaxSilences)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return "", fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	if err = s.setSilence(sil, now, false); err != nil {
		return "", err
	}
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:
	return msil.Silence, true
}

func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) error {
	sil.UpdatedAt = now

	if !skipValidate {
		if err := validateSilence(sil); err != nil {
			return fmt.Errorf("silence invalid: %w", err)
		}
	}

	msil := &pb.MeshSilence{
		Silence:   sil,
		ExpiresAt: sil.EndsAt.Add(s.retention),
	}
	b, err := marshalMeshSilence(msil)
	if err != nil {
		return err
	}

	// Check the limit unless the silence has been expired. This is to avoid
	// situations where silences cannot be expired after the limit has been
	// reduced.
	if n := msil.Size(); s.limits.MaxPerSilenceSize > 0 && n > s.limits.MaxPerSilenceSize && sil.EndsAt.After(now) {
		return fmt.Errorf("silence exceeded maximum size: %d", s.limits.MaxPerSilenceSize)
	}

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) (string, error) {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return "", ErrNotFound
	}

	if ok {
		if canUpdate(prev, sil, now) {
			return sil.Id, s.setSilence(sil, now, false)

Review comment (suggestion):
```suggestion
		return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", msil.Size, s.limits.MaxPerSilenceSize)
```

Code after:
		return nil, false
	}
	return msil.Silence, true
}

func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) error {
	sil.UpdatedAt = now

	if !skipValidate {
		if err := validateSilence(sil); err != nil {
			return fmt.Errorf("silence invalid: %w", err)
		}
	}

	msil := &pb.MeshSilence{
		Silence:   sil,
		ExpiresAt: sil.EndsAt.Add(s.retention),
	}
	b, err := marshalMeshSilence(msil)
	if err != nil {
		return err
	}

	// Check the limit unless the silence has been expired. This is to avoid
	// situations where silences cannot be expired after the limit has been
	// reduced.
	if n := msil.Size(); s.limits.MaxPerSilenceBytes > 0 && n > s.limits.MaxPerSilenceBytes && sil.EndsAt.After(now) {
		return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, s.limits.MaxPerSilenceBytes)
	}

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) (string, error) {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return "", ErrNotFound
	}

	if ok {
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:
		if m := s.limits.MaxSilenceSizeBytes(); m > 0 && n > m && sil.EndsAt.After(now) {
			return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m)
		}
	}

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return ErrNotFound
	}

	if ok && canUpdate(prev, sil, now) {
		return s.setSilence(sil, now, false)
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences != nil {
		if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m {
			return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m)
		}
	}

	if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired {
		// We cannot update the silence, expire the old one.
		if err := s.expire(prev.Id); err != nil {
			return fmt.Errorf("expire previous silence: %w", err)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now

Review comment (suggestion):
This code should run before checking the `MaxSilences()` limit because we don't want to prevent updating an existing silence if we are at the limit and the update does not require expiring and creating a new silence.

Code after:
		if m := s.limits.MaxSilenceSizeBytes(); m > 0 && n > m && sil.EndsAt.After(now) {
			return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m)
		}
	}

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return ErrNotFound
	}

	if ok && canUpdate(prev, sil, now) {
		return s.setSilence(sil, now, false)
	}

	// If we got here it's either a new silence or a replacing one (which would
	// also create a new silence) so we need to make sure we have capacity for
	// the new silence.
	if s.limits.MaxSilences != nil {
		if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m {
			return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m)
		}
	}

	if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired {
		// We cannot update the silence, expire the old one to leave a history of
		// the silence before modification.
		if err := s.expire(prev.Id); err != nil {
			return fmt.Errorf("expire previous silence: %w", err)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()
Repository: prometheus/alertmanager
File: silence/silence.go (Go)

Code before:

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return ErrNotFound
	}

	if ok && canUpdate(prev, sil, now) {
		return s.setSilence(sil, now, false)
	}

	// If we got here it's either a new silence or a replacing one.
	if s.limits.MaxSilences != nil {
		if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m {
			return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m)
		}
	}

	if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired {
		// We cannot update the silence, expire the old one.
		if err := s.expire(prev.Id); err != nil {
			return fmt.Errorf("expire previous silence: %w", err)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}

	return s.setSilence(sil, now, false)
}

Review comment (suggestion):
```suggestion
	// If we got here it's either a new silence or a replacing an existing one (which would also create a new silence) so we need to make sure we have capacity for the new silence.
```

Code after:

	if s.st.merge(msil, now) {
		s.version++
	}
	s.broadcast(b)

	return nil
}

// Set the specified silence. If a silence with the ID already exists and the modification
// modifies history, the old silence gets expired and a new one is created.
func (s *Silences) Set(sil *pb.Silence) error {
	s.mtx.Lock()
	defer s.mtx.Unlock()

	now := s.nowUTC()
	prev, ok := s.getSilence(sil.Id)
	if sil.Id != "" && !ok {
		return ErrNotFound
	}

	if ok && canUpdate(prev, sil, now) {
		return s.setSilence(sil, now, false)
	}

	// If we got here it's either a new silence or a replacing one (which would
	// also create a new silence) so we need to make sure we have capacity for
	// the new silence.
	if s.limits.MaxSilences != nil {
		if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m {
			return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m)
		}
	}

	if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired {
		// We cannot update the silence, expire the old one to leave a history of
		// the silence before modification.
		if err := s.expire(prev.Id); err != nil {
			return fmt.Errorf("expire previous silence: %w", err)
		}
	}

	uid, err := uuid.NewV4()
	if err != nil {
		return fmt.Errorf("generate uuid: %w", err)
	}
	sil.Id = uid.String()

	if sil.StartsAt.Before(now) {
		sil.StartsAt = now
	}
Repository: prometheus/alertmanager
File: config/config_test.go (Go)

Code before:

	err := json.Unmarshal(b, &u)
	if err != nil {
		t.Fatal(err)
	}
	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in JSON.")

	err = yaml.Unmarshal(b, &u)
	if err != nil {
		t.Fatal(err)
	}

	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.")
}

func TestHideSecretURL(t *testing.T) {
	b := []byte(`"://wrongurl/"`)
	var u SecretURL

	err := json.Unmarshal(b, &u)
	if err != nil {
		errStr := err.Error()
		if strings.Contains(errStr, "://wrongurl/") {
			t.Fatal("config's String method reveals authentication credentials.")
		}
	}
}

func TestMarshalURL(t *testing.T) {
	for name, tc := range map[string]struct {
		input        *URL
		expectedJSON string
		expectedYAML string
	}{
		"url": {
			input:        mustParseURL("http://example.com/"),
			expectedJSON: "\"http://example.com/\"",
			expectedYAML: "http://example.com/\n",
		},

		"wrapped nil value": {
			input:        &URL{},
			expectedJSON: "null",
			expectedYAML: "null\n",
		},

		"wrapped empty URL": {
			input:        &URL{&url.URL{}},
			expectedJSON: "\"\"",
			expectedYAML: "\"\"\n",
		},

Review comment (bug):
A bit shorter + ensuring that there's an error. 
```suggestion
	err := json.Unmarshal(b, &u)
	require.Error(t, err)
	require.NotContains(t, err.Error(), "wrongurl")
```

Code after:

	err := json.Unmarshal(b, &u)
	if err != nil {
		t.Fatal(err)
	}
	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in JSON.")

	err = yaml.Unmarshal(b, &u)
	if err != nil {
		t.Fatal(err)
	}

	require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.")
}

func TestHideSecretURL(t *testing.T) {
	b := []byte(`"://wrongurl/"`)
	var u SecretURL

	err := json.Unmarshal(b, &u)
	require.Error(t, err)
	require.NotContains(t, err.Error(), "wrongurl")
}

func TestMarshalURL(t *testing.T) {
	for name, tc := range map[string]struct {
		input        *URL
		expectedJSON string
		expectedYAML string
	}{
		"url": {
			input:        mustParseURL("http://example.com/"),
			expectedJSON: "\"http://example.com/\"",
			expectedYAML: "http://example.com/\n",
		},

		"wrapped nil value": {
			input:        &URL{},
			expectedJSON: "null",
			expectedYAML: "null\n",
		},

		"wrapped empty URL": {
			input:        &URL{&url.URL{}},
			expectedJSON: "\"\"",
			expectedYAML: "\"\"\n",
		},
	} {
		t.Run(name, func(t *testing.T) {
			j, err := json.Marshal(tc.input)
			require.NoError(t, err)
Repository: prometheus/alertmanager
File: silence/silence_test.go (Go)

Code before:
				SnapshotReader: &bytes.Buffer{},
			},
		},
		{
			options: &Options{
				SnapshotFile: "test.bkp",
			},
		},
		{
			options: &Options{
				SnapshotFile:   "test bkp",
				SnapshotReader: &bytes.Buffer{},
			},
			err: "only one of SnapshotFile and SnapshotReader must be set",
		},
	}

	for _, c := range cases {
		checkErr(t, c.err, c.options.validate())
	}
}

func TestSilenceGCOverTime(t *testing.T) {
	type silenceEntry struct {
		s                    *pb.Silence
		expectPresentAfterGc bool
	}
	c := clock.NewMock()
	now := c.Now().UTC()

	newSilence := func(id string, exp time.Time) *pb.Silence {
		return &pb.Silence{
			Id:       id,
			StartsAt: now.Add(-time.Second),
			EndsAt:   exp,
			Matchers: []*pb.Matcher{
				{Name: "foo", Type: pb.Matcher_REGEXP, Pattern: "bar"},
			}}
	}

	// The GC will run with it's clock equal to 1 second after now
	cases := []struct {
		name            string
		initialState    []silenceEntry
		updates         []silenceEntry
		expectedGCCount int
	}{
		{
			name: "gc does not clean active silences",
			initialState: []silenceEntry{
				{s: newSilence("1", now), expectPresentAfterGc: false},

Review comment (suggestion):
I think this is more complicated than it needs to be. Much easier if we invert the bool.

```suggestion
		expectGC bool
```

Code after:
				SnapshotReader: &bytes.Buffer{},
			},
		},
		{
			options: &Options{
				SnapshotFile: "test.bkp",
			},
		},
		{
			options: &Options{
				SnapshotFile:   "test bkp",
				SnapshotReader: &bytes.Buffer{},
			},
			err: "only one of SnapshotFile and SnapshotReader must be set",
		},
	}

	for _, c := range cases {
		checkErr(t, c.err, c.options.validate())
	}
}

func TestSilenceGCOverTime(t *testing.T) {
	t.Run("GC does not remove active silences", func(t *testing.T) {
		s, err := New(Options{})
		require.NoError(t, err)
		s.clock = clock.NewMock()
		now := s.nowUTC()
		s.st = state{
			"1": &pb.MeshSilence{Silence: &pb.Silence{Id: "1"}, ExpiresAt: now},
			"2": &pb.MeshSilence{Silence: &pb.Silence{Id: "2"}, ExpiresAt: now.Add(-time.Second)},
			"3": &pb.MeshSilence{Silence: &pb.Silence{Id: "3"}, ExpiresAt: now.Add(time.Second)},
		}
		want := state{
			"3": &pb.MeshSilence{Silence: &pb.Silence{Id: "3"}, ExpiresAt: now.Add(time.Second)},
		}
		n, err := s.GC()
		require.NoError(t, err)
		require.Equal(t, 2, n)
		require.Equal(t, want, s.st)
	})

	t.Run("GC does not leak cache entries", func(t *testing.T) {
		s, err := New(Options{})
		require.NoError(t, err)
		clock := clock.NewMock()
		s.clock = clock
		sil1 := &pb.Silence{
			Matchers: []*pb.Matcher{{
				Type:    pb.Matcher_EQUAL,
				Name:    "foo",