mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 09:52:06 +08:00
270 lines
6.6 KiB
Go
270 lines
6.6 KiB
Go
package plugin_test
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
task "github.com/langhuihui/gotask"
|
|
"m7s.live/v5"
|
|
"m7s.live/v5/pkg/util"
|
|
"m7s.live/v5/plugin/test/pb"
|
|
)
|
|
|
|
const (
|
|
TestCaseStatusInit TestCaseStatus = "init"
|
|
TestCaseStatusStarting TestCaseStatus = "starting"
|
|
TestCaseStatusRunning TestCaseStatus = "running"
|
|
TestCaseStatusSuccess TestCaseStatus = "success"
|
|
TestCaseStatusFailed TestCaseStatus = "failed"
|
|
)
|
|
|
|
func (f *TestTaskFactory) Register(action string, taskCreator func(*TestCase, TestTaskConfig) task.ITask) {
|
|
f.tasks[action] = taskCreator
|
|
}
|
|
|
|
func (f *TestTaskFactory) Create(taskConfig TestTaskConfig, scenario *TestCase) (task.ITask, error) {
|
|
if taskCreator, exists := f.tasks[taskConfig.Action]; exists {
|
|
return taskCreator(scenario, taskConfig), nil
|
|
}
|
|
return nil, fmt.Errorf("no task registered for action: %s", taskConfig)
|
|
}
|
|
|
|
var testTaskFactory = TestTaskFactory{
|
|
tasks: make(map[string]func(*TestCase, TestTaskConfig) task.ITask),
|
|
}
|
|
|
|
type (
|
|
TestTaskFactory struct {
|
|
tasks map[string]func(*TestCase, TestTaskConfig) task.ITask
|
|
}
|
|
TestTaskConfig struct {
|
|
Action string `json:"action"`
|
|
Delay time.Duration `json:"delay"`
|
|
Format string `json:"format"`
|
|
ServerAddr string `json:"serverAddr" default:"localhost"`
|
|
Input string `json:"input"`
|
|
StreamPath string `json:"streamPath"`
|
|
}
|
|
TestCaseStatus string
|
|
TestConfig struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
VideoCodec string `json:"videoCodec" default:"h264"`
|
|
AudioCodec string `json:"audioCodec" default:"aac"`
|
|
VideoOnly bool `json:"videoOnly"`
|
|
AudioOnly bool `json:"audioOnly"`
|
|
Tags []string `json:"tags"`
|
|
Timeout time.Duration `json:"timeout" default:"30s"`
|
|
Tasks []TestTaskConfig `json:"tasks"`
|
|
}
|
|
TestCase struct {
|
|
*task.Job `json:"-"`
|
|
*TestConfig
|
|
Plugin *TestPlugin `json:"-"`
|
|
Status TestCaseStatus `json:"status"`
|
|
StartTime int64 `json:"startTime"`
|
|
EndTime int64 `json:"endTime"`
|
|
Duration int32 `json:"duration"`
|
|
ErrorMsg string `json:"errorMsg"`
|
|
Logs string `json:"logs"`
|
|
}
|
|
TestPlugin struct {
|
|
pb.UnimplementedApiServer
|
|
m7s.Plugin
|
|
Cases map[string]TestConfig
|
|
testCases map[string]*TestCase
|
|
flushSSE chan struct{}
|
|
// Stress 测试相关字段
|
|
pushers util.Collection[string, *m7s.PushJob]
|
|
pullers util.Collection[string, *m7s.PullJob]
|
|
}
|
|
TestBaseTask struct {
|
|
task.Task
|
|
testCase *TestCase
|
|
TestTaskConfig
|
|
}
|
|
)
|
|
|
|
func (ts *TestCase) Start() (err error) {
|
|
ts.Status = TestCaseStatusStarting
|
|
ts.StartTime = time.Now().Unix()
|
|
return nil
|
|
}
|
|
|
|
func (ts *TestCase) Go() (err error) {
|
|
ts.Status = TestCaseStatusRunning
|
|
ts.Plugin.FlushSSE()
|
|
subTaskSelect := []reflect.SelectCase{
|
|
{
|
|
Dir: reflect.SelectRecv,
|
|
Chan: reflect.ValueOf(time.After(ts.Timeout)),
|
|
},
|
|
{
|
|
Dir: reflect.SelectRecv,
|
|
Chan: reflect.ValueOf(ts.Done()),
|
|
},
|
|
}
|
|
var subTask []task.ITask
|
|
for _, taskConfig := range ts.Tasks {
|
|
if taskConfig.StreamPath == "" {
|
|
taskConfig.StreamPath = fmt.Sprintf("test/%d", ts.ID)
|
|
}
|
|
if taskConfig.Input != "" && !strings.Contains(taskConfig.Input, ".") {
|
|
taskConfig.Input = fmt.Sprintf("%s/%d", taskConfig.Input, ts.ID)
|
|
}
|
|
t, err := testTaskFactory.Create(taskConfig, ts)
|
|
if err != nil {
|
|
ts.Status = TestCaseStatusFailed
|
|
ts.ErrorMsg = fmt.Sprintf("Failed to create test task: %v", err)
|
|
ts.Plugin.FlushSSE()
|
|
return err
|
|
}
|
|
if taskConfig.Delay > 0 {
|
|
subTask = append(subTask, t)
|
|
subTaskSelect = append(subTaskSelect, reflect.SelectCase{
|
|
Dir: reflect.SelectRecv,
|
|
Chan: reflect.ValueOf(time.After(taskConfig.Delay)),
|
|
})
|
|
} else {
|
|
ts.AddDependTask(t)
|
|
}
|
|
}
|
|
for {
|
|
chosen, _, recvOK := reflect.Select(subTaskSelect)
|
|
switch chosen {
|
|
case 0:
|
|
ts.Stop(task.ErrTimeout)
|
|
case 1:
|
|
if errors.Is(ts.StopReason(), task.ErrTaskComplete) {
|
|
ts.Status = TestCaseStatusSuccess
|
|
} else {
|
|
ts.Status = TestCaseStatusFailed
|
|
ts.ErrorMsg = ts.StopReason().Error()
|
|
}
|
|
ts.Plugin.FlushSSE()
|
|
return nil
|
|
default:
|
|
if recvOK {
|
|
ts.AddDependTask(subTask[chosen-2])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dispose 任务停止
|
|
func (ts *TestCase) Dispose() {
|
|
if ts.ErrorMsg == "" {
|
|
ts.Status = TestCaseStatusSuccess
|
|
} else {
|
|
ts.Status = TestCaseStatusFailed
|
|
}
|
|
ts.EndTime = time.Now().Unix()
|
|
ts.Duration = int32(time.Now().Unix() - ts.StartTime)
|
|
ts.Plugin.FlushSSE()
|
|
}
|
|
|
|
func (ts *TestCase) Write(buf []byte) (int, error) {
|
|
ts.Logs += time.Now().Format("2006-01-02 15:04:05") + " " + string(buf) + "\n"
|
|
return len(buf), nil
|
|
}
|
|
|
|
// GetTestCaseFromCache 从缓存获取测试用例
|
|
func (p *TestPlugin) GetTestCaseFromCache(name string) (tc *TestCase, exists bool) {
|
|
p.Call(func() {
|
|
tc, exists = p.testCases[name]
|
|
})
|
|
return
|
|
}
|
|
|
|
func (p *TestPlugin) FlushSSE() {
|
|
select {
|
|
case p.flushSSE <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
type TestCaseFilter struct {
|
|
Tags []string
|
|
Status TestCaseStatus
|
|
Category string
|
|
TestType string
|
|
}
|
|
|
|
var StatusOrder = [...]TestCaseStatus{
|
|
TestCaseStatusRunning,
|
|
TestCaseStatusStarting,
|
|
TestCaseStatusFailed,
|
|
TestCaseStatusSuccess,
|
|
TestCaseStatusInit,
|
|
}
|
|
|
|
func (p *TestPlugin) GetTestCasesFromCache(filter TestCaseFilter) (cases []*TestCase) {
|
|
p.Call(func() {
|
|
for _, tc := range p.testCases {
|
|
// 标签过滤
|
|
if len(filter.Tags) > 0 {
|
|
if !slices.ContainsFunc(filter.Tags, func(tag string) bool {
|
|
return slices.Contains(tc.Tags, tag)
|
|
}) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if filter.Status != "" && tc.Status != filter.Status {
|
|
continue
|
|
}
|
|
cases = append(cases, tc)
|
|
}
|
|
})
|
|
slices.SortFunc(cases, func(a, b *TestCase) int {
|
|
if a.Status == b.Status {
|
|
return strings.Compare(a.Name, b.Name)
|
|
}
|
|
for _, status := range StatusOrder {
|
|
if a.Status == status {
|
|
return -1
|
|
}
|
|
if b.Status == status {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
})
|
|
return
|
|
}
|
|
|
|
//go:embed default.yaml
|
|
var defaultYaml m7s.DefaultYaml
|
|
|
|
var _ = m7s.InstallPlugin[TestPlugin](m7s.PluginMeta{
|
|
ServiceDesc: &pb.Api_ServiceDesc,
|
|
RegisterGRPCHandler: pb.RegisterApiHandler,
|
|
DefaultYaml: defaultYaml,
|
|
})
|
|
|
|
func (p *TestPlugin) Start() error {
|
|
p.testCases = make(map[string]*TestCase)
|
|
for name, tc := range p.Cases {
|
|
tc.Name = name
|
|
p.testCases[name] = &TestCase{
|
|
TestConfig: &tc,
|
|
Plugin: p,
|
|
Status: TestCaseStatusInit,
|
|
}
|
|
}
|
|
p.flushSSE = make(chan struct{}, 1)
|
|
return nil
|
|
}
|
|
|
|
func (p *TestPlugin) RegisterHandler() map[string]http.HandlerFunc {
|
|
return map[string]http.HandlerFunc{
|
|
"/sse/cases": p.GetTestCaseSSE,
|
|
}
|
|
}
|