cloud-game/cmd/main_test.go
giongto35 ad82e22cfc
Integrate with RetroArch to have Universal emulator. (#64)
* WIP

* Add libretro

* Update input retro

* Update cloud retro input:

* Integrate libretro remove GL

* Launch emulator by game type

* Save and load

* Update deeplink to game directly

* Done channel for naemulator

* each server handle only one session

* Only provide available clients

* Emulator based on game

* Remove all OpenGL related

* Retroarch audio

* Audio encoding mono

* Experiment with libretro audio

* Audio without datachannel

* Audio opus internal

* Remove unnecessary code

* Resampled

* Limit time frame

* Start Room and Audio by game

* Dynamic video image allocation

* Dynamic Video buffer allocation based on video resolution

* Move encoder to room

* width and height from emulator

* Padding images to fix multipler of 64

* Remove OpenGL

* Remove Open AL

* Remove OpenGL from Go mod

* Move away nanoarch logic in naemulator

* Remove unecessary savefiles.go

* Optimize Docker for caching

* Update ReadME

* Update README to introduce Retro

* Update README

* Update README.md
2019-08-02 22:06:26 +08:00

515 lines
12 KiB
Go

package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/giongto35/cloud-game/cws"
"github.com/giongto35/cloud-game/overlord"
gamertc "github.com/giongto35/cloud-game/webrtc"
"github.com/giongto35/cloud-game/worker"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v2"
)
var host = "http://localhost:8000"
// Test is in cmd, so gamePath is in parent path
var testGamePath = "../games"
var webrtcconfig = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{
URLs: []string{"stun:stun.l.google.com:19302"},
}}}
func initOverlord() (*httptest.Server, *httptest.Server) {
server := overlord.NewServer()
overlordWorker := httptest.NewServer(http.HandlerFunc(server.WSO))
overlordBrowser := httptest.NewServer(http.HandlerFunc(server.WS))
return overlordWorker, overlordBrowser
}
func initWorker(t *testing.T, overlordURL string) *worker.Handler {
fmt.Println("Spawn new worker")
if overlordURL == "" {
return nil
} else {
overlordURL = "ws" + strings.TrimPrefix(overlordURL, "http")
fmt.Println("connecting to overlord: ", overlordURL)
}
handler := worker.NewHandler(overlordURL, testGamePath)
go handler.Run()
time.Sleep(time.Second)
return handler
}
func initClient(t *testing.T, host string) (client *cws.Client) {
// Convert http://127.0.0.1 to ws://127.0.0.
u := "ws" + strings.TrimPrefix(host, "http")
fmt.Println("Connecting to", u)
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
if err != nil {
t.Fatalf("%v", err)
}
handshakedone := make(chan struct{})
// Simulate peerconnection initialization from client
fmt.Println("Simulating PeerConnection")
peerConnection, err := webrtc.NewPeerConnection(webrtcconfig)
if err != nil {
t.Fatalf("%v", err)
}
offer, err := peerConnection.CreateOffer(nil)
if err != nil {
t.Fatalf("%v", err)
}
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(offer)
if err != nil {
panic(err)
}
// Send offer to overlord
log.Println("Browser Client")
client = cws.NewClient(ws)
go client.Listen()
fmt.Println("Sending offer...")
client.Send(cws.WSPacket{
ID: "initwebrtc",
Data: gamertc.Encode(offer),
}, nil)
fmt.Println("Waiting sdp...")
client.Receive("sdp", func(resp cws.WSPacket) cws.WSPacket {
log.Println("Received SDP", resp.Data, "client: ", client)
answer := webrtc.SessionDescription{}
gamertc.Decode(resp.Data, &answer)
// Apply the answer as the remote description
err = peerConnection.SetRemoteDescription(answer)
if err != nil {
panic(err)
}
// TODO: may block in the second call
handshakedone <- struct{}{}
return cws.EmptyPacket
})
// Request Offer routing
client.Receive("requestOffer", func(resp cws.WSPacket) cws.WSPacket {
log.Println("Frontend received requestOffer")
peerConnection, err = webrtc.NewPeerConnection(webrtcconfig)
if err != nil {
t.Fatalf("%v", err)
}
log.Println("Recreating offer")
offer, err := peerConnection.CreateOffer(nil)
if err != nil {
t.Fatalf("%v", err)
}
log.Println("Set localDesc")
err = peerConnection.SetLocalDescription(offer)
if err != nil {
panic(err)
}
log.Println("return offer")
return cws.WSPacket{
ID: "initwebrtc",
Data: gamertc.Encode(offer),
}
})
<-handshakedone
return client
// If receive roomID, the server is running correctly
}
func TestSingleServerOneOverlord(t *testing.T) {
/*
Case scenario:
- A server X are initilized
- Client join room with coordinator
Expected behavior:
- Room received not empty.
*/
oworker, obrowser := initOverlord()
defer obrowser.Close()
defer oworker.Close()
// Init worker
worker := initWorker(t, oworker.URL)
defer worker.Close()
// connect overlord
client := initClient(t, obrowser.URL)
defer client.Close()
fmt.Println("Sending start...")
roomID := make(chan string)
client.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: "",
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
roomID <- resp.RoomID
})
respRoomID := <-roomID
if respRoomID == "" {
fmt.Println("RoomID should not be empty")
t.Fail()
}
time.Sleep(time.Second)
fmt.Println("Done")
}
func TestTwoServerOneOverlord(t *testing.T) {
/*
Case scenario:
- Two server X, Y are initilized
- Client A creates a room on server X
- Client B creates a room on server Y
- Client B join a room created by A
Expected behavior:
- Bridge connection will be conducted between server Y and X
- Client B can join a room hosted on A
*/
oworker, obrowser := initOverlord()
defer obrowser.Close()
defer oworker.Close()
worker1 := initWorker(t, oworker.URL)
defer worker1.Close()
worker2 := initWorker(t, oworker.URL)
defer worker2.Close()
client1 := initClient(t, obrowser.URL)
defer client1.Close()
roomID := make(chan string)
client1.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: "",
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
roomID <- resp.RoomID
})
remoteRoomID := <-roomID
if remoteRoomID == "" {
fmt.Println("RoomID should not be empty")
t.Fail()
}
fmt.Println("Done create a room in server 1")
// ------------------------------------
// Client2 trying to create a random room and later join the the room on server1
client2 := initClient(t, obrowser.URL)
defer client2.Close()
// Wait
// Doing the same create local room.
localRoomID := make(chan string)
client2.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: "",
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
localRoomID <- resp.RoomID
})
<-localRoomID
fmt.Println("Request the room from server 1", remoteRoomID)
log.Println("Server2 trying to join server1 room")
// After trying loging in to one session, login to other with the roomID
bridgeRoom := make(chan string)
client2.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: remoteRoomID,
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
bridgeRoom <- resp.RoomID
})
<-bridgeRoom
//respRoomID := <-bridgeRoom
//if respRoomID == "" {
//fmt.Println("The room ID should be equal to the saved room")
//t.Fail()
//}
// If receive roomID, the server is running correctly
time.Sleep(time.Second)
fmt.Println("Done")
}
func TestReconnectRoom(t *testing.T) {
/*
Case scenario:
- A server X is initialized connecting to overlord
- Client A creates a room K on server X
- Server X is turned down, Client is closed
- Spawn a new server and a new client connecting to the same room K
Expected behavior:
- The game should be continue
TODO: Current test just make sure the game is running, not check if the game is the same
*/
oworker, obrowser := initOverlord()
defer obrowser.Close()
defer oworker.Close()
// Init worker
worker := initWorker(t, oworker.URL)
client := initClient(t, obrowser.URL)
fmt.Println("Sending start...")
roomID := make(chan string)
client.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: "",
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
roomID <- resp.RoomID
})
saveRoomID := <-roomID
if saveRoomID == "" {
fmt.Println("RoomID should not be empty")
t.Fail()
}
log.Println("Closing room and server")
client.Close()
worker.GetOverlordClient().Close()
worker.Close()
// Close server and reconnect
log.Println("Server respawn")
// Init slave server again
worker = initWorker(t, oworker.URL)
defer worker.Close()
client = initClient(t, obrowser.URL)
defer client.Close()
fmt.Println("Re-access room ", saveRoomID)
roomID = make(chan string)
client.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: saveRoomID,
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
roomID <- resp.RoomID
})
respRoomID := <-roomID
if respRoomID == "" || respRoomID != saveRoomID {
fmt.Println("The room ID should be equal to the saved room")
t.Fail()
}
time.Sleep(time.Second)
fmt.Println("Done")
}
func TestReconnectRoomNoLocal(t *testing.T) {
/*
Case scenario:
- A server X is initialized connecting to overlord
- Client A creates a room K on server X
- Server X is turned down, Client is closed
- room on local is deleted
- Spawn a new server and a new client connecting to the same room K
Expected behavior:
- room on local storage is refetched from cloud storage
- The game should be continue where it is closed
TODO: Current test just make sure the game is running, not check if the game is the same
*/
// This test only run if GCP storage is set
oworker, obrowser := initOverlord()
defer obrowser.Close()
defer oworker.Close()
// Init worker
ggCredential := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
if ggCredential == "" {
return
}
worker := initWorker(t, oworker.URL)
client := initClient(t, obrowser.URL)
fmt.Println("Sending start...")
roomID := make(chan string)
client.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: "",
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
roomID <- resp.RoomID
})
saveRoomID := <-roomID
if saveRoomID == "" {
fmt.Println("RoomID should not be empty")
t.Fail()
}
log.Println("Closing room and server")
client.Close()
worker.GetOverlordClient().Close()
worker.Close()
// Remove room on local
path := util.GetSavePath(saveRoomID)
os.Remove(path)
// Expect Google cloud call
// Close server and reconnect
log.Println("Server respawn")
// Init slave server again
worker = initWorker(t, oworker.URL)
defer worker.Close()
client = initClient(t, obrowser.URL)
defer client.Close()
fmt.Println("Re-access room ", saveRoomID)
roomID = make(chan string)
client.Send(cws.WSPacket{
ID: "start",
Data: "Contra.nes",
RoomID: saveRoomID,
PlayerIndex: 1,
}, func(resp cws.WSPacket) {
fmt.Println("RoomID:", resp.RoomID)
roomID <- resp.RoomID
})
respRoomID := <-roomID
if respRoomID == "" || respRoomID != saveRoomID {
fmt.Println("The room ID should be equal to the saved room")
t.Fail()
}
time.Sleep(time.Second)
fmt.Println("Done")
}
//func TestRejoinNoOverlordMultiple(t *testing.T) {
//[>
//Case scenario:
//- A server X without connecting to overlord
//- Client A keeps creating a new room
//Expected behavior:
//- The game should running normally
//*/
//// Init slave server
//s := initServer(t, nil)
//defer s.Close()
//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine())
//client := initClient(t, s.URL)
//for i := 0; i < 100; i++ {
//fmt.Println("Sending start...")
//// Keep starting game
//roomID := make(chan string)
//client.Send(cws.WSPacket{
//ID: "start",
//Data: "Contra.nes",
//RoomID: "",
//PlayerIndex: 1,
//}, func(resp cws.WSPacket) {
//fmt.Println("RoomID:", resp.RoomID)
//roomID <- resp.RoomID
//})
//respRoomID := <-roomID
//if respRoomID == "" {
//fmt.Println("The room ID should be equal to the saved room")
//t.Fail()
//}
//}
//time.Sleep(time.Second)
//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine())
//fmt.Println("Done")
//}
//func TestRejoinWithOverlordMultiple(t *testing.T) {
//[>
//Case scenario:
//- A server X is initialized connecting to overlord
//- Client A keeps creating a new room
//Expected behavior:
//- The game should running normally
//*/
//// Init slave server
//o := initOverlord()
//defer o.Close()
//oconn := connectTestOverlordServer(t, o.URL)
//// Init slave server
//s := initServer(t, oconn)
//defer s.Close()
//fmt.Println("Num goRoutine before start: ", runtime.NumGoroutine())
//client := initClient(t, s.URL)
//for i := 0; i < 100; i++ {
//fmt.Println("Sending start...")
//// Keep starting game
//roomID := make(chan string)
//client.Send(cws.WSPacket{
//ID: "start",
//Data: "Contra.nes",
//RoomID: "",
//PlayerIndex: 1,
//}, func(resp cws.WSPacket) {
//fmt.Println("RoomID:", resp.RoomID)
//roomID <- resp.RoomID
//})
//respRoomID := <-roomID
//if respRoomID == "" {
//fmt.Println("The room ID should be equal to the saved room")
//t.Fail()
//}
//}
//fmt.Println("Num goRoutine should be small: ", runtime.NumGoroutine())
//fmt.Println("Done")
//}