diff --git a/.github/workflows/coder.yaml b/.github/workflows/coder.yaml index 65635c322acff..b8b9f5c59cb8e 100644 --- a/.github/workflows/coder.yaml +++ b/.github/workflows/coder.yaml @@ -149,11 +149,19 @@ jobs: - run: go install gotest.tools/gotestsum@latest - - run: + - name: Test with Mock Database + run: gotestsum --jsonfile="gotests.json" --packages="./..." -- -covermode=atomic -coverprofile="gotests.coverage" -timeout=3m -count=3 -race -parallel=2 + - name: Test with PostgreSQL Database + if: runner.os == 'Linux' + run: + DB=true gotestsum --jsonfile="gotests.json" --packages="./..." -- + -covermode=atomic -coverprofile="gotests.coverage" -timeout=3m + -count=1 -race -parallel=2 + - uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index d3e880133e2c6..3c9968fbebad6 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -2,8 +2,10 @@ package coderdtest import ( "context" + "database/sql" "net/http/httptest" "net/url" + "os" "testing" "github.com/stretchr/testify/require" @@ -11,7 +13,9 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/coderd" "github.com/coder/coder/codersdk" + "github.com/coder/coder/database" "github.com/coder/coder/database/databasefake" + "github.com/coder/coder/database/postgres" ) // Server represents a test instance of coderd. @@ -27,6 +31,20 @@ type Server struct { func New(t *testing.T) Server { // This can be hotswapped for a live database instance. db := databasefake.New() + if os.Getenv("DB") != "" { + connectionURL, close, err := postgres.Open() + require.NoError(t, err) + t.Cleanup(close) + sqlDB, err := sql.Open("postgres", connectionURL) + require.NoError(t, err) + t.Cleanup(func() { + _ = sqlDB.Close() + }) + err = database.Migrate(sqlDB) + require.NoError(t, err) + db = database.New(sqlDB) + } + handler := coderd.New(&coderd.Options{ Logger: slogtest.Make(t, nil), Database: db, diff --git a/database/query.sql b/database/query.sql index 3382f86c5e801..68a6fa52f2f52 100644 --- a/database/query.sql +++ b/database/query.sql @@ -86,13 +86,14 @@ INSERT INTO email, name, login_type, + revoked, hashed_password, created_at, updated_at, username ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *; + ($1, $2, $3, $4, false, $5, $6, $7, $8) RETURNING *; -- name: UpdateAPIKeyByID :exec UPDATE diff --git a/database/query.sql.go b/database/query.sql.go index 7d1003b8b2094..f4b4fc753419d 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -243,13 +243,14 @@ INSERT INTO email, name, login_type, + revoked, hashed_password, created_at, updated_at, username ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, email, name, revoked, login_type, hashed_password, created_at, updated_at, temporary_password, avatar_hash, ssh_key_regenerated_at, username, dotfiles_git_uri, roles, status, relatime, gpg_key_regenerated_at, _decomissioned, shell + ($1, $2, $3, $4, false, $5, $6, $7, $8) RETURNING id, email, name, revoked, login_type, hashed_password, created_at, updated_at, temporary_password, avatar_hash, ssh_key_regenerated_at, username, dotfiles_git_uri, roles, status, relatime, gpg_key_regenerated_at, _decomissioned, shell ` type InsertUserParams struct { diff --git a/peer/channel.go b/peer/channel.go index 2d5f62c006255..2d9859a04c6ac 100644 --- a/peer/channel.go +++ b/peer/channel.go @@ -135,6 +135,7 @@ func (c *Channel) init() { c.conn.dcDisconnectListeners.Add(1) c.conn.dcFailedListeners.Add(1) + c.conn.dcClosedWaitGroup.Add(1) go func() { var err error // A DataChannel can disconnect multiple times, so this needs to loop. @@ -274,6 +275,7 @@ func (c *Channel) closeWithError(err error) error { close(c.sendMore) c.conn.dcDisconnectListeners.Sub(1) c.conn.dcFailedListeners.Sub(1) + c.conn.dcClosedWaitGroup.Done() if c.rwc != nil { _ = c.rwc.Close() diff --git a/peer/conn.go b/peer/conn.go index 721e6f0f450b9..ff399f7c6e805 100644 --- a/peer/conn.go +++ b/peer/conn.go @@ -113,6 +113,7 @@ type Conn struct { dcDisconnectListeners atomic.Uint32 dcFailedChannel chan struct{} dcFailedListeners atomic.Uint32 + dcClosedWaitGroup sync.WaitGroup localCandidateChannel chan webrtc.ICECandidateInit localSessionDescriptionChannel chan webrtc.SessionDescription @@ -125,11 +126,10 @@ type Conn struct { pingEchoChan *Channel pingEchoOnce sync.Once pingEchoError error - - pingMutex sync.Mutex - pingOnce sync.Once - pingChan *Channel - pingError error + pingMutex sync.Mutex + pingOnce sync.Once + pingChan *Channel + pingError error } func (c *Conn) init() error { @@ -502,5 +502,10 @@ func (c *Conn) CloseWithError(err error) error { // this call will return an error that isn't typed. We don't check the error because // closing an already closed connection isn't an issue for us. _ = c.rtc.Close() + + // Waits for all DataChannels to exit before officially labeling as closed. + // All logging, goroutines, and async functionality is cleaned up after this. + c.dcClosedWaitGroup.Wait() + return err }