diff --git a/cmd/omegaup-grader/frontend_handler.go b/cmd/omegaup-grader/frontend_handler.go index 44666b2..e719c47 100644 --- a/cmd/omegaup-grader/frontend_handler.go +++ b/cmd/omegaup-grader/frontend_handler.go @@ -254,21 +254,22 @@ func broadcastRun( message.Contest = *run.Contest } type serializedRun struct { - User string `json:"username"` - Contest *string `json:"contest_alias,omitempty"` - Problemset *int64 `json:"problemset,omitempty"` - Problem string `json:"alias"` - GUID string `json:"guid"` - Runtime float64 `json:"runtime"` - Penalty float64 `json:"penalty"` - Memory base.Byte `json:"memory"` - Score float64 `json:"score"` - ContestScore float64 `json:"contest_score"` - Status string `json:"status"` - Verdict string `json:"verdict"` - SubmitDelay float64 `json:"submit_delay"` - Time float64 `json:"time"` - Language string `json:"language"` + User string `json:"username"` + Contest *string `json:"contest_alias,omitempty"` + Problemset *int64 `json:"problemset,omitempty"` + Problem string `json:"alias"` + GUID string `json:"guid"` + Runtime float64 `json:"runtime"` + Penalty float64 `json:"penalty"` + Memory base.Byte `json:"memory"` + Score float64 `json:"score"` + ContestScore float64 `json:"contest_score"` + Status string `json:"status"` + Verdict string `json:"verdict"` + SubmitDelay float64 `json:"submit_delay"` + Time float64 `json:"time"` + Language string `json:"language"` + ScorePerGroup map[string]float64 `json:"score_per_group"` } type runFinishedMessage struct { Message string `json:"message"` @@ -283,20 +284,21 @@ func broadcastRun( msg := runFinishedMessage{ Message: "/run/update/", Run: serializedRun{ - Contest: run.Contest, - Problemset: run.Problemset, - Problem: run.Run.ProblemName, - GUID: run.GUID, - Runtime: run.Result.Time, - Memory: run.Result.Memory, - Score: score, - ContestScore: contestScore, - Status: "ready", - Verdict: run.Result.Verdict, - Language: run.Run.Language, - Time: -1, - SubmitDelay: -1, - Penalty: -1, + Contest: run.Contest, + Problemset: run.Problemset, + Problem: run.Run.ProblemName, + GUID: run.GUID, + Runtime: run.Result.Time, + Memory: run.Result.Memory, + Score: score, + ContestScore: contestScore, + Status: "ready", + Verdict: run.Result.Verdict, + Language: run.Run.Language, + Time: -1, + SubmitDelay: -1, + Penalty: -1, + ScorePerGroup: make(map[string]float64), }, } var runTime time.Time @@ -320,6 +322,29 @@ func broadcastRun( if err != nil { return err } + rows, err := queryWithRetry( + db, + `SELECT + rg.group_name, rg.score + FROM + Runs_Groups rg + WHERE + rg.run_id = ?;`, run.ID) + if err != nil { + return fmt.Errorf("failed to query run groups: %w", err) + } + + scores := make(map[string]float64) + for rows.Next() { + var groupName string + var scoreByGroup float64 + err = rows.Scan(&groupName, &scoreByGroup) + if err != nil { + return fmt.Errorf("failed to scan run group: %w", err) + } + scores[groupName] = scoreByGroup + } + msg.Run.ScorePerGroup = scores msg.Run.Time = float64(runTime.Unix()) message.User = msg.Run.User if run.Problemset != nil { diff --git a/cmd/omegaup-grader/frontend_handler_test.go b/cmd/omegaup-grader/frontend_handler_test.go index a83be18..9c1127e 100644 --- a/cmd/omegaup-grader/frontend_handler_test.go +++ b/cmd/omegaup-grader/frontend_handler_test.go @@ -7,6 +7,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "reflect" "testing" _ "github.com/mattn/go-sqlite3" @@ -194,6 +195,21 @@ func newInMemoryDB(t *testing.T, scoreMode string) *sql.DB { ) VALUES ( 1, 1, "1", "1", "JE", "1970-01-01 00:00:00" ); + INSERT INTO Runs_Groups ( + case_run_id, run_id, group_name, score, verdict + ) VALUES ( + 1, 1, "easy", 0.1, "PA" + ); + INSERT INTO Runs_Groups ( + case_run_id, run_id, group_name, score, verdict + ) VALUES ( + 2, 1, "medium", 0.2, "PA" + ); + INSERT INTO Runs_Groups ( + case_run_id, run_id, group_name, score, verdict + ) VALUES ( + 3, 1, "sample", 0.3, "PA" + ); INSERT INTO Identities ( identity_id, username ) VALUES ( @@ -230,7 +246,7 @@ func TestUpdateDatabase(t *testing.T) { ); err != nil { t.Fatalf("Error updating the database: %v", err) } - if count != 0 { + if count != 3 { t.Errorf("Wrong number of rows in the database. found %v, want %v", count, 0) } @@ -318,7 +334,7 @@ func TestUpdateDatabase(t *testing.T) { ); err != nil { t.Fatalf("Error updating the database: %v", err) } - if count != 2 { + if count != 5 { t.Errorf("Wrong number of rows in the database. found %v, want %v", count, 2) } if err := queryRowWithRetry( @@ -341,10 +357,52 @@ func TestBroadcastRun(t *testing.T) { verdict string score *big.Rat expectedScore float64 + scorePerGroup map[string]any }{ - {scoreMode: "partial", verdict: "AC", score: big.NewRat(1, 1), expectedScore: 1.}, - {scoreMode: "partial", verdict: "PA", score: big.NewRat(1, 2), expectedScore: 0.5}, - {scoreMode: "all_or_nothing", verdict: "PA", score: big.NewRat(1, 2), expectedScore: 0.}, + { + scoreMode: "partial", + verdict: "AC", + score: big.NewRat(1, 1), + expectedScore: 1., + scorePerGroup: map[string]any{ + "easy": 0.1, + "medium": 0.2, + "sample": 0.3, + }, + }, + { + scoreMode: "partial", + verdict: "PA", + score: big.NewRat(1, 2), + expectedScore: 0.5, + scorePerGroup: map[string]any{ + "easy": 0.1, + "medium": 0.2, + "sample": 0.3, + }, + }, + { + scoreMode: "all_or_nothing", + verdict: "PA", + score: big.NewRat(1, 2), + expectedScore: 0., + scorePerGroup: map[string]any{ + "easy": 0.1, + "medium": 0.2, + "sample": 0.3, + }, + }, + { + scoreMode: "max_per_group", + verdict: "PA", + score: big.NewRat(1, 3), + expectedScore: 0.3333333333333333, + scorePerGroup: map[string]any{ + "easy": 0.1, + "medium": 0.2, + "sample": 0.3, + }, + }, } for idx, s := range scenarios { t.Run(fmt.Sprintf("%d: verdict=%s scoreMode=%s", idx, s.verdict, s.scoreMode), func(t *testing.T) { @@ -394,6 +452,12 @@ func TestBroadcastRun(t *testing.T) { t.Fatalf("Message does not contain a run entry: %v", encodedMessage) } + if !reflect.DeepEqual(s.scorePerGroup, runInfo["score_per_group"]) { + t.Errorf("message.score_per_group=%v (type %T), want %v (type %T)", + runInfo["score_per_group"], runInfo["score_per_group"], + s.scorePerGroup, s.scorePerGroup) + } + for key, value := range map[string]any{ "guid": "1", "status": "ready",