diff --git a/app/gojudge/internal/biz/gojudge.go b/app/gojudge/internal/biz/gojudge.go index b5f6b9b..438b8ef 100644 --- a/app/gojudge/internal/biz/gojudge.go +++ b/app/gojudge/internal/biz/gojudge.go @@ -36,7 +36,7 @@ func (g *GoJudge) Compile(code []byte, language string, requestID string) (strin Cmd: []*pb.Request_CmdType{ { Args: command.Compile, - Env: []string{"PATH=/usr/bin:/bin"}, + Env: command.Env, Files: []*pb.Request_File{ requestMemory([]byte{}), requestPipe("stdout", command.CompileConfig.StdoutMaxSize), diff --git a/app/gojudge/internal/biz/gojudge_test.go b/app/gojudge/internal/biz/gojudge_test.go index 02fc4ea..953bfc7 100644 --- a/app/gojudge/internal/biz/gojudge_test.go +++ b/app/gojudge/internal/biz/gojudge_test.go @@ -2,13 +2,14 @@ package biz import ( "context" - "github.com/go-kratos/kratos/v2/log" - "github.com/go-kratos/kratos/v2/transport/grpc" pb "sastoj/api/sastoj/gojudge/judger/gojudge/v1" "sastoj/app/gojudge/internal/conf" "sastoj/app/gojudge/internal/data" "sastoj/pkg/util" "testing" + + "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport/grpc" ) // TestHandleSubmit require start with the env: go-judge(diff languages) @@ -30,6 +31,9 @@ func TestCGoJudge(t *testing.T) { commands := data.NewCommands( []string{"C", "C++"}, + map[string][]string{ + "default": {"PATH=/usr/bin:/bin"}, + }, map[string]string{ "C": "/usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm", "C++": "/usr/bin/g++ -Wall -std=c++14 -o foo foo.cc -lm -I/include", @@ -122,6 +126,9 @@ func TestBashGoJudge(t *testing.T) { commands := data.NewCommands( []string{"Bash"}, + map[string][]string{ + "default": {"PATH=/usr/bin:/bin"}, + }, map[string]string{}, map[string]string{ "Bash": "/bin/bash foo.sh", @@ -185,3 +192,409 @@ func TestBashGoJudge(t *testing.T) { panic(err) } } + +// TODO: fix Java build +func TestJavaGoJudge(t *testing.T) { + language := "Java" + code := `import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int a = sc.nextInt(); + int b = sc.nextInt(); + System.out.println(a + b); + } +}` + inputs := []string{"1 2\n", "3 4\n"} + endpoint := "127.0.0.1:5051" + + //connect to go-judge + ClientConn, err := grpc.DialInsecure( + context.Background(), + grpc.WithEndpoint(endpoint), + grpc.WithHealthCheck(false)) + if err != nil { + panic(err) + } + exec := pb.NewExecutorClient(ClientConn) + + enable := []string{"Java"} + env := map[string][]string{ + "default": {"PATH=/usr/bin:/bin"}, + } + compile := map[string]string{ + "Java": `/usr/bin/bash -c "/usr/bin/javac Main.java && /usr/bin/jar cvf Main.jar *.class > /dev/null"`, + } + run := map[string]string{ + "Java": "/usr/bin/java Main.jar", + } + source := map[string]string{ + "Java": "Main.java", + } + target := map[string]string{ + "Java": "Main.jar", + } + configs := map[string]*conf.LanguageConfig{ + "default": { + Compile: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + Run: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + }, + } + command := data.NewCommands( + enable, + env, + compile, + run, + source, + target, + configs, + ) + + goJudge := GoJudge{ + client: &exec, + commands: &command, + } + + //compile + fileID, res, err := goJudge.Compile([]byte(code), language, "1") + if err != nil { + log.Errorf("failed compile file: %v \nres :%v", err, res) + panic(err) + } + log.Infof("compiled fileID: %v", fileID) + + //judge + for _, input := range inputs { + judge, err := goJudge.Judge([]byte(input), language, fileID, "2", 10000000000, 10000000000*2, 104857600, 1240000) + if err != nil { + log.Errorf("failed running judge: %v", err) + panic(err) + } + log.Infof("judge: %v", judge) + } + + //delete compiled file + err = goJudge.DeleteFile(fileID) + if err != nil { + log.Errorf("failed deleting file: %v", err) + panic(err) + } +} + +func TestGolangGoJudge(t *testing.T) { + language := "Golang" + code := `package main + +import ( + "fmt" +) + +func main() { + var a, b int + fmt.Scanf("%d %d", &a, &b) + fmt.Println(a + b) +}` + inputs := []string{"1 2\n", "3 4\n"} + endpoint := "127.0.0.1:5051" + + //connect to go-judge + ClientConn, err := grpc.DialInsecure( + context.Background(), + grpc.WithEndpoint(endpoint), + grpc.WithHealthCheck(false)) + if err != nil { + panic(err) + } + exec := pb.NewExecutorClient(ClientConn) + + enable := []string{"Golang"} + env := map[string][]string{ + "Golang": {"PATH=/usr/bin:/bin", "GOCACHE=/tmp/"}, + } + compile := map[string]string{ + "Golang": "/usr/bin/go build -o foo foo.go", + } + run := map[string]string{ + "default": "foo", + } + source := map[string]string{ + "Golang": "foo.go", + } + target := map[string]string{ + "Golang": "foo", + } + configs := map[string]*conf.LanguageConfig{ + "default": { + Compile: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + Run: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + }, + } + command := data.NewCommands( + enable, + env, + compile, + run, + source, + target, + configs, + ) + + goJudge := GoJudge{ + client: &exec, + commands: &command, + } + + //compile + fileID, res, err := goJudge.Compile([]byte(code), language, "1") + if err != nil { + log.Errorf("failed compile file: %v \nres :%v", err, res) + panic(err) + } + log.Infof("compiled fileID: %v", fileID) + + //judge + for _, input := range inputs { + judge, err := goJudge.Judge([]byte(input), language, fileID, "2", 10000000000, 10000000000*2, 104857600, 1240000) + if err != nil { + log.Errorf("failed running judge: %v", err) + panic(err) + } + log.Infof("judge: %v", judge) + } + + //delete compiled file + err = goJudge.DeleteFile(fileID) + if err != nil { + log.Errorf("failed deleting file: %v", err) + panic(err) + } +} + +func TestPython3GoJudge(t *testing.T) { + language := "Python" + code := `a, b = map(int, input().split()) +print(a + b)` + inputs := []string{"1 2\n", "3 4\n"} + endpoint := "127.0.0.1:5051" + + //connect to go-judge + ClientConn, err := grpc.DialInsecure( + context.Background(), + grpc.WithEndpoint(endpoint), + grpc.WithHealthCheck(false)) + if err != nil { + panic(err) + } + exec := pb.NewExecutorClient(ClientConn) + + enable := []string{"Python"} + env := map[string][]string{ + "Python": {"PATH=/usr/bin:/bin"}, + } + compile := map[string]string{ + "Python": "", + } + run := map[string]string{ + "Python": "/usr/bin/python3 foo.py", + } + source := map[string]string{ + "Python": "foo.py", + } + target := map[string]string{ + "Python": "foo.py", + } + configs := map[string]*conf.LanguageConfig{ + "default": { + Compile: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + Run: &conf.ExecConfig{ + ProcLimit: 50, + CpuTimeLimit: 10000000000, + CpuRateLimit: 10000000000, + ClockTimeLimit: 100000000000, + MemoryLimit: 104857600, + StdoutMaxSize: 100000000, + StderrMaxSize: 100000000, + }, + }, + } + command := data.NewCommands( + enable, + env, + compile, + run, + source, + target, + configs, + ) + + goJudge := GoJudge{ + client: &exec, + commands: &command, + } + + //compile + fileID, res, err := goJudge.Compile([]byte(code), language, "1") + if err != nil { + log.Errorf("failed compile file: %v \nres :%v", err, res) + panic(err) + } + log.Infof("compiled fileID: %v", fileID) + + //judge + for _, input := range inputs { + judge, err := goJudge.Judge([]byte(input), language, fileID, "2", 10000000000, 10000000000*2, 104857600, 1240000) + if err != nil { + log.Errorf("failed running judge: %v", err) + panic(err) + } + log.Infof("judge: %v", judge) + } + + //delete compiled file + err = goJudge.DeleteFile(fileID) + if err != nil { + log.Errorf("failed deleting file: %v", err) + panic(err) + } +} + +func TestPHPGoJudge(t *testing.T) { + language := "PHP" + code := ` kratos.api.Data 5, // 1: kratos.api.Bootstrap.judge_middleware:type_name -> kratos.api.JudgeMiddleware 2, // 2: kratos.api.Data.database:type_name -> kratos.api.Database @@ -749,23 +815,25 @@ var file_app_gojudge_internal_conf_conf_proto_depIdxs = []int32{ 11, // 8: kratos.api.Language.source:type_name -> kratos.api.Language.SourceEntry 12, // 9: kratos.api.Language.target:type_name -> kratos.api.Language.TargetEntry 13, // 10: kratos.api.Language.exec_config:type_name -> kratos.api.Language.ExecConfigEntry - 8, // 11: kratos.api.LanguageConfig.compile:type_name -> kratos.api.ExecConfig - 8, // 12: kratos.api.LanguageConfig.run:type_name -> kratos.api.ExecConfig - 7, // 13: kratos.api.Language.ExecConfigEntry.value:type_name -> kratos.api.LanguageConfig - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name -} - -func init() { file_app_gojudge_internal_conf_conf_proto_init() } -func file_app_gojudge_internal_conf_conf_proto_init() { - if File_app_gojudge_internal_conf_conf_proto != nil { + 15, // 11: kratos.api.Language.env:type_name -> kratos.api.Language.EnvEntry + 8, // 12: kratos.api.LanguageConfig.compile:type_name -> kratos.api.ExecConfig + 8, // 13: kratos.api.LanguageConfig.run:type_name -> kratos.api.ExecConfig + 7, // 14: kratos.api.Language.ExecConfigEntry.value:type_name -> kratos.api.LanguageConfig + 14, // 15: kratos.api.Language.EnvEntry.value:type_name -> kratos.api.Language.Env + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name +} + +func init() { file_conf_conf_proto_init() } +func file_conf_conf_proto_init() { + if File_conf_conf_proto != nil { return } if !protoimpl.UnsafeEnabled { - file_app_gojudge_internal_conf_conf_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Bootstrap); i { case 0: return &v.state @@ -777,7 +845,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Data); i { case 0: return &v.state @@ -789,7 +857,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*Database); i { case 0: return &v.state @@ -801,7 +869,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Redis); i { case 0: return &v.state @@ -813,7 +881,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Load); i { case 0: return &v.state @@ -825,7 +893,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*JudgeMiddleware); i { case 0: return &v.state @@ -837,7 +905,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*Language); i { case 0: return &v.state @@ -849,7 +917,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*LanguageConfig); i { case 0: return &v.state @@ -861,7 +929,7 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } - file_app_gojudge_internal_conf_conf_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_conf_conf_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*ExecConfig); i { case 0: return &v.state @@ -873,23 +941,35 @@ func file_app_gojudge_internal_conf_conf_proto_init() { return nil } } + file_conf_conf_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*Language_Env); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_app_gojudge_internal_conf_conf_proto_rawDesc, + RawDescriptor: file_conf_conf_proto_rawDesc, NumEnums: 0, - NumMessages: 14, + NumMessages: 16, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_app_gojudge_internal_conf_conf_proto_goTypes, - DependencyIndexes: file_app_gojudge_internal_conf_conf_proto_depIdxs, - MessageInfos: file_app_gojudge_internal_conf_conf_proto_msgTypes, + GoTypes: file_conf_conf_proto_goTypes, + DependencyIndexes: file_conf_conf_proto_depIdxs, + MessageInfos: file_conf_conf_proto_msgTypes, }.Build() - File_app_gojudge_internal_conf_conf_proto = out.File - file_app_gojudge_internal_conf_conf_proto_rawDesc = nil - file_app_gojudge_internal_conf_conf_proto_goTypes = nil - file_app_gojudge_internal_conf_conf_proto_depIdxs = nil + File_conf_conf_proto = out.File + file_conf_conf_proto_rawDesc = nil + file_conf_conf_proto_goTypes = nil + file_conf_conf_proto_depIdxs = nil } diff --git a/app/gojudge/internal/conf/conf.proto b/app/gojudge/internal/conf/conf.proto index 076af4f..6dfa546 100644 --- a/app/gojudge/internal/conf/conf.proto +++ b/app/gojudge/internal/conf/conf.proto @@ -43,6 +43,10 @@ message Language{ map source = 4; map target = 5; map exec_config = 6; + message Env { + repeated string env = 1; + } + map env = 7; } message LanguageConfig{ diff --git a/app/gojudge/internal/data/data.go b/app/gojudge/internal/data/data.go index 7fb97af..2a5f781 100644 --- a/app/gojudge/internal/data/data.go +++ b/app/gojudge/internal/data/data.go @@ -2,25 +2,24 @@ package data import ( "context" - "entgo.io/ent/dialect" - "entgo.io/ent/dialect/sql" "errors" "fmt" - "github.com/go-kratos/kratos/v2/transport/grpc" - amqp "github.com/rabbitmq/amqp091-go" - "github.com/redis/go-redis/v9" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" pbc "sastoj/api/sastoj/gojudge/judger/gojudge/v1" + "sastoj/app/gojudge/internal/conf" "sastoj/ent" "time" - "sastoj/app/gojudge/internal/conf" - + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport/grpc" "github.com/google/wire" _ "github.com/lib/pq" + amqp "github.com/rabbitmq/amqp091-go" + "github.com/redis/go-redis/v9" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) // ProviderSet is data providers. @@ -43,9 +42,15 @@ func NewData(c *conf.Bootstrap, logger log.Logger) (*Data, func(), error) { logHelper := log.NewHelper(log.With(logger, "module", "data")) logHelper.Infof("Read Go-Judge Conf Success: Endpoint=%s", c.JudgeMiddleware.Endpoint) + + envs := make(map[string][]string) + for s := range c.JudgeMiddleware.Language.Env { + envs[s] = c.JudgeMiddleware.Language.Env[s].Env + } //commands to judge command := NewCommands( c.JudgeMiddleware.Language.Enable, + envs, c.JudgeMiddleware.Language.Compile, c.JudgeMiddleware.Language.Run, c.JudgeMiddleware.Language.Source, diff --git a/app/gojudge/internal/data/language.go b/app/gojudge/internal/data/language.go index 03124ca..54a129a 100644 --- a/app/gojudge/internal/data/language.go +++ b/app/gojudge/internal/data/language.go @@ -9,6 +9,7 @@ type Commands = map[string]Command type Command struct { Compile []string + Env []string Run []string Source string Target string @@ -18,6 +19,7 @@ type Command struct { func NewCommands( enable []string, + env map[string][]string, compile map[string]string, run map[string]string, source map[string]string, @@ -26,6 +28,10 @@ func NewCommands( res := make(map[string]Command) for _, language := range enable { + e, ok := env[language] + if !ok { + e = env["default"] + } r, ok := run[language] if !ok { r = run["default"] @@ -44,17 +50,32 @@ func NewCommands( if !ok { t = target["default"] } - e, ok := configs[language] + config, ok := configs[language] if !ok { - e = configs["default"] + config = configs["default"] + } + + compileCmd := strings.Fields(c) + index := strings.Index(c, "\"") + if index != -1 { + compileCmd = strings.Fields(c[:index]) + compileCmd = append(compileCmd, c[index:]) + } + runCmd := strings.Fields(r) + index = strings.Index(r, "\"") + if index != -1 { + runCmd = strings.Fields(r[:index]) + runCmd = append(runCmd, r[index:]) } + res[language] = Command{ - Compile: strings.Fields(c), - Run: strings.Fields(r), + Env: e, + Compile: compileCmd, + Run: runCmd, Source: s, Target: t, - CompileConfig: e.Compile, - RunConfig: e.Run, + CompileConfig: config.Compile, + RunConfig: config.Run, } } return res diff --git a/conf/gojudge/example.yaml b/conf/gojudge/example.yaml index 4af5f23..82db01a 100644 --- a/conf/gojudge/example.yaml +++ b/conf/gojudge/example.yaml @@ -8,7 +8,7 @@ data: write_timeout: 0.2s db: 0 mq: amqp://sastoj:123456@mq:5672/ - load: + load: problem_cases_location: /data/cases judge_middleware: @@ -25,6 +25,16 @@ judge_middleware: - C++17 - C++17(O2) - Bash + - Java + - Golang + - Python3 + - PHP + env: + "default": + - "PATH=/usr/bin" + "Golang": + - "PATH=/usr/bin:bin" + - "GOCACHE=/tmp/" exec_config: default: compile: