Skip to content

chore: Add test helpers to improve coverage #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Improve workspace history logs
  • Loading branch information
kylecarbs committed Feb 5, 2022
commit c50cee0ed662919622d754f338c06e9be8346d5b
12 changes: 5 additions & 7 deletions coderd/workspacehistorylogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,16 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
})
if errors.Is(err, sql.ErrNoRows) {
err = nil
logs = []database.WorkspaceHistoryLog{}
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get workspace history: %s", err),
})
return
}
if logs == nil {
logs = []database.WorkspaceHistoryLog{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is just to get the JSON to return an empty array instead of nil?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly!

}
Comment on lines +97 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is the same issue I saw with projects? Sometimes, I'd get nil for projects returned - even in the case where was no error (and had to do something similar): https://github.com/coder/coder/pull/140/files#r797267887

I didn't get a chance to dive deeper yet (I'll look at it next week) - but wondering if its the same issue here? And maybe a bug with our query or sqlc integration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it is! Not an issue with our sqlc implementation, just a false assumption on my part! We now test for this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, good to know! I didn't realize that there could be a case where those functions could return nil (I thought they'd always return an empty array) - but something I'll watch out for now.

render.Status(r, http.StatusOK)
render.JSON(rw, r, logs)
return
Expand All @@ -113,12 +115,8 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
select {
case bufferedLogs <- log:
default:
// This is a case that shouldn't happen, but totally could.
// There's no way to stream data from the database, so we'll
// need to maintain some level of internal buffer.
//
// If this overflows users could miss logs when streaming.
// We warn to make sure we know when it happens!
// If this overflows users could miss logs streaming. This can happen
// if a database request takes a long amount of time, and we get a lot of logs.
api.Logger.Warn(r.Context(), "workspace history log overflowing channel")
}
}
Expand Down
169 changes: 104 additions & 65 deletions coderd/workspacehistorylogs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,93 +9,132 @@ import (

"github.com/coder/coder/coderd"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/database"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
)

func TestWorkspaceHistoryLogs(t *testing.T) {
func TestWorkspaceHistoryLogsByName(t *testing.T) {
t.Parallel()

setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) {
project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: "banana",
Provisioner: database.ProvisionerTypeEcho,
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
})
require.NoError(t, err)
workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "example",
ProjectID: project.ID,
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
Transition: database.WorkspaceTransitionCreate,
})
require.NoError(t, err)
return project, workspace
}

setupProjectVersion := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectVersion {
projectVersion, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
StorageMethod: database.ProjectStorageMethodInlineArchive,
StorageSource: data,
})
// Successfully return empty logs before the job starts!
logs, err := client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
require.NoError(t, err)
require.Eventually(t, func() bool {
hist, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, projectVersion.Name)
require.NoError(t, err)
return hist.Import.Status.Completed()
}, 15*time.Second, 50*time.Millisecond)
return projectVersion
}
require.NotNil(t, logs)
require.Len(t, logs, 0)

client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.NewProvisionerDaemon(t, client)
project, workspace := setupProjectAndWorkspace(t, client, user)
data, err := echo.Tar(&echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
Output: "test",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
})
require.NoError(t, err)
projectVersion := setupProjectVersion(t, client, user, project, data)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)

workspaceHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: projectVersion.ID,
Transition: database.WorkspaceTransitionCreate,
// Return the log after completion!
logs, err = client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
require.NoError(t, err)
require.NotNil(t, logs)
require.Len(t, logs, 1)
})
require.NoError(t, err)

now := database.Now()
logChan, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, workspaceHistory.Name, now)
require.NoError(t, err)

for {
log, more := <-logChan
if !more {
break
}
t.Logf("Output: %s", log.Output)
}

t.Run("ReturnAll", func(t *testing.T) {
t.Run("StreamAfterComplete", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
before := time.Now().UTC()
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
Transition: database.WorkspaceTransitionCreate,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)

_, err := client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, workspaceHistory.Name)
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, before)
require.NoError(t, err)
log := <-logs
require.Equal(t, "log-output", log.Output)
// Make sure the channel automatically closes!
_, ok := <-logs
require.False(t, ok)
})

t.Run("Between", func(t *testing.T) {
t.Run("StreamWhileRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
Transition: database.WorkspaceTransitionCreate,
})
require.NoError(t, err)

_, err := client.WorkspaceHistoryLogsBetween(context.Background(), "", workspace.Name, workspaceHistory.Name, time.Time{}, database.Now())
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, time.Time{})
require.NoError(t, err)
log := <-logs
require.Equal(t, "log-output", log.Output)
// Make sure the channel automatically closes!
_, ok := <-logs
require.False(t, ok)
})
}