Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 15 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,22 @@ jobs:
New-Item -ItemType Directory -Force desktop/src-tauri/binaries | Out-Null
New-Item -ItemType File -Force "desktop/src-tauri/binaries/agentsview-$hostTriple.exe" | Out-Null

- name: Fetch Windows desktop Rust dependencies
shell: pwsh
run: |
for ($attempt = 1; $attempt -le 3; $attempt++) {
cargo fetch --locked --manifest-path desktop/src-tauri/Cargo.toml
if ($LASTEXITCODE -eq 0) {
break
}
if ($attempt -eq 3) {
exit $LASTEXITCODE
}
Start-Sleep -Seconds (10 * $attempt)
}

- name: Run Windows desktop update tests
run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib install_downloaded_update
run: cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml --lib install_downloaded_update

coverage:
runs-on: ubuntu-latest
Expand Down
61 changes: 61 additions & 0 deletions ci_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package agentsview_test

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

type githubWorkflow struct {
Jobs map[string]githubWorkflowJob `yaml:"jobs"`
}

type githubWorkflowJob struct {
Steps []githubWorkflowStep `yaml:"steps"`
}

type githubWorkflowStep struct {
Name string `yaml:"name"`
Run string `yaml:"run"`
}

func TestWindowsDesktopUpdateTestsRetryCargoNetworkFailures(t *testing.T) {
contents, err := os.ReadFile(".github/workflows/ci.yml")
require.NoError(t, err)

var workflow githubWorkflow
require.NoError(t, yaml.Unmarshal(contents, &workflow))

job, ok := workflow.Jobs["desktop-windows-unit"]
require.True(t, ok, "desktop-windows-unit job must exist")

fetchIndex, fetchStep := findWorkflowStep(t, job, "Fetch Windows desktop Rust dependencies")
testIndex, testStep := findWorkflowStep(t, job, "Run Windows desktop update tests")
require.Less(t, fetchIndex, testIndex, "dependencies must be fetched before cargo test")

assert.Contains(t, fetchStep.Run, "cargo fetch --locked --manifest-path desktop/src-tauri/Cargo.toml")
assert.Contains(t, fetchStep.Run, "$attempt")
assert.Contains(t, fetchStep.Run, "$LASTEXITCODE")
assert.Contains(t, fetchStep.Run, "Start-Sleep")
assert.Contains(t, testStep.Run, "cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml --lib install_downloaded_update")
}

func findWorkflowStep(
t *testing.T,
job githubWorkflowJob,
name string,
) (int, githubWorkflowStep) {
t.Helper()

for i, step := range job.Steps {
if step.Name == name {
return i, step
}
}

require.Failf(t, "missing workflow step", "step %q was not found", name)
return -1, githubWorkflowStep{}
}
14 changes: 11 additions & 3 deletions cmd/agentsview/serve_background_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@ import (
)

const (
detachedProcess = 0x00000008
createNewProcessGroup = 0x00000200
createNoWindow = 0x08000000
)

func configureServeBackgroundCommand(cmd *exec.Cmd) {
// Use CREATE_NO_WINDOW rather than DETACHED_PROCESS. A detached
// process has no console at all, so every console child it spawns
// (notably git, invoked while resolving a session's repo during
// sync) forces Windows to allocate a brand-new console window that
// flashes on screen. CREATE_NO_WINDOW instead gives the daemon a
// hidden console that those children inherit, so they run without
// popping a window. CREATE_NEW_PROCESS_GROUP still isolates it from
// the launching terminal's Ctrl-C.
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: detachedProcess | createNewProcessGroup,
CreationFlags: createNoWindow | createNewProcessGroup,
}
}

// terminateProcess stops the server. Windows does not deliver POSIX signals to
// a detached process, so there is no graceful equivalent of SIGTERM here; the
// a background process, so there is no graceful equivalent of SIGTERM here; the
// process is killed and serve stop removes its runtime record afterward.
func terminateProcess(proc *os.Process) error {
return proc.Kill()
Expand Down
25 changes: 25 additions & 0 deletions cmd/agentsview/serve_background_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build windows

package main

import (
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const testDetachedProcess = 0x00000008

func TestConfigureServeBackgroundCommandUsesHiddenConsole(t *testing.T) {
cmd := exec.Command("agentsview", "serve", "--background")

configureServeBackgroundCommand(cmd)

require.NotNil(t, cmd.SysProcAttr)
flags := cmd.SysProcAttr.CreationFlags
assert.NotZero(t, flags&createNoWindow)
assert.NotZero(t, flags&createNewProcessGroup)
assert.Zero(t, flags&testDetachedProcess)
}