mirror of
https://github.com/redis/go-redis.git
synced 2025-07-29 17:41:15 +03:00
feat: Enable CI for Redis CE 8.0 (#3274)
* chore: extract benchmark tests * wip * enable pubsub tests * enable ring tests * stop tests with build redis from source * start all tests * mix of makefile and action * add sentinel configs * fix example test * stop debug on re * wip * enable gears for redis 7.2 * wip * enable sentinel, they are expected to fail * fix: linter configuration * chore: update re versions * return older redis enterprise version * add basic codeql * wip: increase timeout, focus only sentinel tests * sentinels with docker network host * enable all tests * fix flanky test * enable example tests * tidy docker compose * add debug output * stop shutingdown masters * don't test sentinel for re * skip unsuported addscores * Update README bump go version in CI * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update CONTRIBUTING.md add information about new test setup --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
226
main_test.go
226
main_test.go
@ -4,9 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -28,12 +27,12 @@ const (
|
||||
|
||||
const (
|
||||
sentinelName = "go-redis-test"
|
||||
sentinelMasterPort = "9123"
|
||||
sentinelSlave1Port = "9124"
|
||||
sentinelSlave2Port = "9125"
|
||||
sentinelPort1 = "9126"
|
||||
sentinelPort2 = "9127"
|
||||
sentinelPort3 = "9128"
|
||||
sentinelMasterPort = "9121"
|
||||
sentinelSlave1Port = "9122"
|
||||
sentinelSlave2Port = "9123"
|
||||
sentinelPort1 = "26379"
|
||||
sentinelPort2 = "26380"
|
||||
sentinelPort3 = "26381"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -49,19 +48,15 @@ var (
|
||||
var (
|
||||
sentinelAddrs = []string{":" + sentinelPort1, ":" + sentinelPort2, ":" + sentinelPort3}
|
||||
|
||||
processes map[string]*redisProcess
|
||||
|
||||
redisMain *redisProcess
|
||||
ringShard1, ringShard2, ringShard3 *redisProcess
|
||||
sentinelMaster, sentinelSlave1, sentinelSlave2 *redisProcess
|
||||
sentinel1, sentinel2, sentinel3 *redisProcess
|
||||
ringShard1, ringShard2, ringShard3 *redis.Client
|
||||
sentinelMaster, sentinelSlave1, sentinelSlave2 *redis.Client
|
||||
sentinel1, sentinel2, sentinel3 *redis.Client
|
||||
)
|
||||
|
||||
var cluster = &clusterScenario{
|
||||
ports: []string{"16600", "16601", "16602", "16603", "16604", "16605"},
|
||||
nodeIDs: make([]string, 6),
|
||||
processes: make(map[string]*redisProcess, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
ports: []string{"16600", "16601", "16602", "16603", "16604", "16605"},
|
||||
nodeIDs: make([]string, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
}
|
||||
|
||||
// Redis Software Cluster
|
||||
@ -70,30 +65,23 @@ var RECluster = false
|
||||
// Redis Community Edition Docker
|
||||
var RCEDocker = false
|
||||
|
||||
// Notes the major version of redis we are executing tests.
|
||||
// Notes version of redis we are executing tests against.
|
||||
// This can be used before we change the bsm fork of ginkgo for one,
|
||||
// which have support for label sets, so we can filter tests per redis major version.
|
||||
var RedisMajorVersion = 7
|
||||
// which have support for label sets, so we can filter tests per redis version.
|
||||
var RedisVersion float64 = 7.2
|
||||
|
||||
func SkipBeforeRedisMajor(version int, msg string) {
|
||||
if RedisMajorVersion < version {
|
||||
Skip(fmt.Sprintf("(redis major version < %d) %s", version, msg))
|
||||
func SkipBeforeRedisVersion(version float64, msg string) {
|
||||
if RedisVersion < version {
|
||||
Skip(fmt.Sprintf("(redis version < %f) %s", version, msg))
|
||||
}
|
||||
}
|
||||
|
||||
func SkipAfterRedisMajor(version int, msg string) {
|
||||
if RedisMajorVersion > version {
|
||||
Skip(fmt.Sprintf("(redis major version > %d) %s", version, msg))
|
||||
func SkipAfterRedisVersion(version float64, msg string) {
|
||||
if RedisVersion > version {
|
||||
Skip(fmt.Sprintf("(redis version > %f) %s", version, msg))
|
||||
}
|
||||
}
|
||||
|
||||
func registerProcess(port string, p *redisProcess) {
|
||||
if processes == nil {
|
||||
processes = make(map[string]*redisProcess)
|
||||
}
|
||||
processes[port] = p
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
addr := os.Getenv("REDIS_PORT")
|
||||
if addr != "" {
|
||||
@ -104,35 +92,33 @@ var _ = BeforeSuite(func() {
|
||||
RECluster, _ = strconv.ParseBool(os.Getenv("RE_CLUSTER"))
|
||||
RCEDocker, _ = strconv.ParseBool(os.Getenv("RCE_DOCKER"))
|
||||
|
||||
RedisMajorVersion, _ = strconv.Atoi(os.Getenv("REDIS_MAJOR_VERSION"))
|
||||
RedisVersion, _ = strconv.ParseFloat(strings.Trim(os.Getenv("REDIS_VERSION"), "\""), 64)
|
||||
|
||||
if RedisMajorVersion == 0 {
|
||||
RedisMajorVersion = 7
|
||||
if RedisVersion == 0 {
|
||||
RedisVersion = 7.2
|
||||
}
|
||||
|
||||
fmt.Printf("RECluster: %v\n", RECluster)
|
||||
fmt.Printf("RCEDocker: %v\n", RCEDocker)
|
||||
fmt.Printf("REDIS_MAJOR_VERSION: %v\n", RedisMajorVersion)
|
||||
fmt.Printf("REDIS_VERSION: %v\n", RedisVersion)
|
||||
|
||||
if RedisMajorVersion < 6 || RedisMajorVersion > 8 {
|
||||
panic("incorrect or not supported redis major version")
|
||||
if RedisVersion < 7.0 || RedisVersion > 9 {
|
||||
panic("incorrect or not supported redis version")
|
||||
}
|
||||
|
||||
if !RECluster && !RCEDocker {
|
||||
|
||||
redisMain, err = startRedis(redisPort)
|
||||
redisPort = redisStackPort
|
||||
redisAddr = redisStackAddr
|
||||
if !RECluster {
|
||||
ringShard1, err = connectTo(ringShard1Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ringShard1, err = startRedis(ringShard1Port)
|
||||
ringShard2, err = connectTo(ringShard2Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ringShard2, err = startRedis(ringShard2Port)
|
||||
ringShard3, err = connectTo(ringShard3Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ringShard3, err = startRedis(ringShard3Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelMaster, err = startRedis(sentinelMasterPort)
|
||||
sentinelMaster, err = connectTo(sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinel1, err = startSentinel(sentinelPort1, sentinelName, sentinelMasterPort)
|
||||
@ -144,24 +130,20 @@ var _ = BeforeSuite(func() {
|
||||
sentinel3, err = startSentinel(sentinelPort3, sentinelName, sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelSlave1, err = startRedis(
|
||||
sentinelSlave1Port, "--slaveof", "127.0.0.1", sentinelMasterPort)
|
||||
sentinelSlave1, err = connectTo(sentinelSlave1Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelSlave2, err = startRedis(
|
||||
sentinelSlave2Port, "--slaveof", "127.0.0.1", sentinelMasterPort)
|
||||
err = sentinelSlave1.SlaveOf(ctx, "127.0.0.1", sentinelMasterPort).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = startCluster(ctx, cluster)
|
||||
sentinelSlave2, err = connectTo(sentinelSlave2Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
} else {
|
||||
redisPort = redisStackPort
|
||||
redisAddr = redisStackAddr
|
||||
|
||||
if !RECluster {
|
||||
// populate cluster node information
|
||||
Expect(configureClusterTopology(ctx, cluster)).NotTo(HaveOccurred())
|
||||
}
|
||||
err = sentinelSlave2.SlaveOf(ctx, "127.0.0.1", sentinelMasterPort).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// populate cluster node information
|
||||
Expect(configureClusterTopology(ctx, cluster)).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
@ -169,12 +151,6 @@ var _ = AfterSuite(func() {
|
||||
if !RECluster {
|
||||
Expect(cluster.Close()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
// NOOP if there are no processes registered
|
||||
for _, p := range processes {
|
||||
Expect(p.Close()).NotTo(HaveOccurred())
|
||||
}
|
||||
processes = nil
|
||||
})
|
||||
|
||||
func TestGinkgoSuite(t *testing.T) {
|
||||
@ -204,7 +180,7 @@ func redisOptions() *redis.Options {
|
||||
}
|
||||
return &redis.Options{
|
||||
Addr: redisAddr,
|
||||
DB: 15,
|
||||
DB: 0,
|
||||
|
||||
DialTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
@ -256,7 +232,9 @@ func performAsync(n int, cbs ...func(int)) *sync.WaitGroup {
|
||||
var wg sync.WaitGroup
|
||||
for _, cb := range cbs {
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
// start from 1, so we can skip db 0 where such test is executed with
|
||||
// select db command
|
||||
for i := 1; i <= n; i++ {
|
||||
go func(cb func(int), i int) {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
@ -313,15 +291,6 @@ func eventually(fn func() error, timeout time.Duration) error {
|
||||
}
|
||||
}
|
||||
|
||||
func execCmd(name string, args ...string) (*os.Process, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
if testing.Verbose() {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
return cmd.Process, cmd.Start()
|
||||
}
|
||||
|
||||
func connectTo(port string) (*redis.Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: ":" + port,
|
||||
@ -338,117 +307,22 @@ func connectTo(port string) (*redis.Client, error) {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type redisProcess struct {
|
||||
*os.Process
|
||||
*redis.Client
|
||||
}
|
||||
|
||||
func (p *redisProcess) Close() error {
|
||||
if err := p.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := eventually(func() error {
|
||||
if err := p.Client.Ping(ctx).Err(); err != nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("client %s is not shutdown", p.Options().Addr)
|
||||
}, 10*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Client.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server"))
|
||||
redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "redis.conf"))
|
||||
redisSentinelConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "sentinel.conf"))
|
||||
)
|
||||
|
||||
func redisDir(port string) (string, error) {
|
||||
dir, err := filepath.Abs(filepath.Join("testdata", "instances", port))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o775); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func startRedis(port string, args ...string) (*redisProcess, error) {
|
||||
dir, err := redisDir(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := exec.Command("cp", "-f", redisServerConf, dir).Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir, "--enable-module-command", "yes"}
|
||||
process, err := execCmd(redisServerBin, append(baseArgs, args...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func startSentinel(port, masterName, masterPort string) (*redis.Client, error) {
|
||||
client, err := connectTo(port)
|
||||
if err != nil {
|
||||
process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &redisProcess{process, client}
|
||||
registerProcess(port, p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func startSentinel(port, masterName, masterPort string) (*redisProcess, error) {
|
||||
dir, err := redisDir(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sentinelConf := filepath.Join(dir, "sentinel.conf")
|
||||
if err := os.WriteFile(sentinelConf, nil, 0o644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
process, err := execCmd(redisServerBin, sentinelConf, "--sentinel", "--port", port, "--dir", dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := connectTo(port)
|
||||
if err != nil {
|
||||
process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set down-after-milliseconds=2000
|
||||
// link: https://github.com/redis/redis/issues/8607
|
||||
for _, cmd := range []*redis.StatusCmd{
|
||||
redis.NewStatusCmd(ctx, "SENTINEL", "MONITOR", masterName, "127.0.0.1", masterPort, "2"),
|
||||
redis.NewStatusCmd(ctx, "SENTINEL", "SET", masterName, "down-after-milliseconds", "2000"),
|
||||
redis.NewStatusCmd(ctx, "SENTINEL", "SET", masterName, "failover-timeout", "1000"),
|
||||
redis.NewStatusCmd(ctx, "SENTINEL", "SET", masterName, "parallel-syncs", "1"),
|
||||
} {
|
||||
client.Process(ctx, cmd)
|
||||
if err := cmd.Err(); err != nil {
|
||||
process.Kill()
|
||||
if err := cmd.Err(); err != nil && !strings.Contains(err.Error(), "ERR Duplicate master name.") {
|
||||
return nil, fmt.Errorf("%s failed: %w", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
p := &redisProcess{process, client}
|
||||
registerProcess(port, p)
|
||||
return p, nil
|
||||
return client, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user