Skip to content
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

ci(gui): Add end-to-end test scenarios where GUI plays a role #306

Merged
merged 31 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d2b587e
Allows selecting the Flutter entry point
CarlosNihelton Oct 4, 2023
373f1c9
Allows building the msix in Debug mode
CarlosNihelton Oct 4, 2023
02e25eb
Sync stdio if attaching to a parent console app
CarlosNihelton Sep 29, 2023
851741f
FlutterWindow to be notified of test completion
CarlosNihelton Oct 4, 2023
ed38b26
Implements the Flutter automation for e2e
CarlosNihelton Oct 4, 2023
b7956cb
Moves some test utilities for sharing
CarlosNihelton Oct 4, 2023
fbe5497
Implements getCurrentFuncName
CarlosNihelton Oct 4, 2023
92628a0
Allows CLI args and env overrides to startAgent
CarlosNihelton Oct 4, 2023
15c14e2
Updates org token test to use the CLI arg
CarlosNihelton Oct 4, 2023
87226c0
Implements the manual token input test case
CarlosNihelton Oct 4, 2023
a2c9191
Corrects the path of the built artifacts
CarlosNihelton Oct 4, 2023
54e7aae
Hard coded pat to VCLibs Debug appx
CarlosNihelton Oct 4, 2023
0b32a40
getCurrentFuncName() not needed due t.Name()
CarlosNihelton Oct 6, 2023
3ac6d0e
Ensures the same wait time on both test paths
CarlosNihelton Oct 6, 2023
45046f7
Reduces the amount of time sleeping
CarlosNihelton Oct 6, 2023
3a2f6b5
Print journal only if test failed
CarlosNihelton Oct 6, 2023
339e39b
CI now builds both Debug and Release modes in matrix
CarlosNihelton Oct 6, 2023
bae0864
OverrideEntrypoint property to recognize 'false'
CarlosNihelton Oct 6, 2023
003b7ed
Updates the build Appx tool to support build modes
CarlosNihelton Oct 6, 2023
c2fe805
Updates installation docs
CarlosNihelton Oct 6, 2023
cfec9cb
Fix grammar in the download artifacts step comment
CarlosNihelton Oct 6, 2023
2fce225
Setup debug vclibs to its own step
CarlosNihelton Oct 6, 2023
800bfb3
Leave download artifacts without the 'name' parameter
CarlosNihelton Oct 6, 2023
072b42a
Remove .deb and .msix* extensions in install doc
CarlosNihelton Oct 6, 2023
61e6ee2
Context as parameter for distroIsProAttached()
CarlosNihelton Oct 6, 2023
a4ecd04
Fix Debug vs Release logic in the msix build
CarlosNihelton Oct 6, 2023
3aa4177
Del tasks.json commited by mistake :)
CarlosNihelton Oct 9, 2023
f85c2a1
Restores time.Sleep in the no-attach case.
CarlosNihelton Oct 9, 2023
9090edd
Only logs journal from the current boot
CarlosNihelton Oct 11, 2023
a84f602
Unify 15s timeout in the end of tests
CarlosNihelton Oct 9, 2023
d394180
30s max timeout
CarlosNihelton Oct 11, 2023
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
44 changes: 27 additions & 17 deletions .github/workflows/qa-azure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
UP4W_SKIP_INTERNAL_DEPENDENCY_UPDATE: "1"

build-ubuntu-pro-for-windows:
strategy:
matrix:
mode: ["production", "end_to_end_tests"]
name: Build Windows Agent Appx
runs-on: windows-latest
steps:
Expand Down Expand Up @@ -64,26 +67,28 @@ jobs:
Import-PfxCertificate -Password $pwd -CertStoreLocation Cert:LocalMachine\Trust -FilePath certificate\certificate.pfx
Import-PfxCertificate -Password $pwd -CertStoreLocation Cert:CurrentUser\My -FilePath certificate\certificate.pfx
- name: Build Bundle
run: |
msbuild `
.\msix\msix.sln `
-target:Build `
-maxCpuCount `
-nodeReuse:false `
-property:Configuration=Release `
-property:AppxBundle=Always `
-property:AppxBundlePlatforms=x64 `
-property:UapAppxPackageBuildMode=SideloadOnly `
-property:Version=0.0.1+${{ github.sha }} `
-nologo `
run: |
msbuild `
.\msix\msix.sln `
-target:Build `
-maxCpuCount `
-nodeReuse:false `
-property:Configuration=${{ matrix.mode == 'production' && 'Release' || 'Debug'}} `
-property:AppxBundle=Always `
-property:AppxBundlePlatforms=x64 `
-property:UapAppxPackageBuildMode=SideloadOnly `
-property:Version=0.0.1+${{ github.sha }} `
-property:UP4W_END_TO_END=${{ matrix.mode == 'end_to_end_tests' }} `
-nologo `
-verbosity:normal

- name: Upload sideload artifact
uses: actions/upload-artifact@v3
with:
name: UbuntuProForWindows+${{ github.sha }}
name: UbuntuProForWindows+${{ github.sha }}-${{ matrix.mode }}
path: |
msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64.cer
msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64.msixbundle
msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*.cer
msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*.msixbundle

vm-setup:
name: Set up Azure VM
Expand Down Expand Up @@ -137,8 +142,8 @@ jobs:
Get-AppxPackage -Name "CanonicalGroupLimited.UbuntuProForWindows" | Remove-AppxPackage -ErrorAction Ignore

New-Item -Name "windows-agent" -ItemType Directory
Move-Item -Path "UbuntuProForWindows+*/UbuntuProForWindows_*/*.msixbundle" -Destination "windows-agent/"
Move-Item -Path "UbuntuProForWindows+*/UbuntuProForWindows_*/*.cer" -Destination "windows-agent/"
Move-Item -Path "UbuntuProForWindows+*-end_to_end_tests/UbuntuProForWindows_*/*.msixbundle" -Destination "windows-agent/"
Move-Item -Path "UbuntuProForWindows+*-end_to_end_tests/UbuntuProForWindows_*/*.cer" -Destination "windows-agent/"
Remove-Item -Recurse "UbuntuProForWindows+*/"

$cert = "$(Get-ChildItem windows-agent/UbuntuProForWindows_*.cer)"
Expand All @@ -151,6 +156,11 @@ jobs:
Move-Item -Path "wsl-pro-service_*/wsl-pro-service_*.deb" -Destination "wsl-pro-service/"
Remove-Item -Recurse "wsl-pro-service_*/"
Write-Output "::endgroup::"

# Installing a debug version of VCLibs from the SDK is required, otherwise installing the Ubuntu pro debug appx will fail.
- name: Install Debug version of VCLibs
shell: powershell
run: Add-AppxPackage "C:\Program Files (x86)\Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Debug\x64\Microsoft.VCLibs.x64.Debug.14.00.Desktop.appx"
- name: Test
shell: powershell
env:
Expand Down
6 changes: 4 additions & 2 deletions doc/02.-Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ This guide will show you how to install Ubuntu Pro For Windows for local develop
2. Click the latest successful workflow run.
3. Scroll down past any warnings or errors, until you reach the Artifacts section.
4. Download:
- Windows agent: UbuntuProForWindows+...
- WSL-Pro-Service: Wsl-pro-service_…
- Windows agent: UbuntuProForWindows+...-production
- wsl-pro-service: Wsl-pro-service_...

Notice that, for the step above, there is also an alternative version of the MSIX bundle enabled for end-to-end testing. Most likely, that's not what you want to download.

### Install the Windows agent
This is the Windows-side agent that manages the distros.
Expand Down
91 changes: 91 additions & 0 deletions end-to-end/manual_token_input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package endtoend_test

import (
"context"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"
wsl "github.com/ubuntu/gowsl"
)

func TestManualTokenInput(t *testing.T) {
type whenToken int
const (
never whenToken = iota
beforeDistroRegistration
afterDistroRegistration
)

// Let's be lazy and don't fall into the risk of changing the function name without updating the places where its name is used.
currentFuncName := t.Name()

testCases := map[string]struct {
whenToken whenToken
overrideTokenEnv string

wantAttached bool
}{
"Success when applying pro token before registration": {whenToken: beforeDistroRegistration, wantAttached: true},
"Success when applying pro token after registration": {whenToken: afterDistroRegistration, wantAttached: true},

"Error with invalid token": {whenToken: afterDistroRegistration, overrideTokenEnv: fmt.Sprintf("%s=%s", proTokenEnv, "CJd8MMN8wXSWsv7wJT8c8dDK")},
}

for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
ctx := context.Background()

testSetup(t)

// Either runs the ubuntupro app before...
if tc.whenToken == beforeDistroRegistration {
cleanup := startAgent(t, ctx, currentFuncName, tc.overrideTokenEnv)
defer cleanup()
}

// Distro setup
name := registerFromTestImage(t, ctx)
d := wsl.NewDistro(ctx, name)

defer func() {
if t.Failed() {
logWslProServiceJournal(t, ctx, d)
}
}()

out, err := d.Command(ctx, "exit 0").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)

// ... or after registration, but never both.
if tc.whenToken == afterDistroRegistration {
cleanup := startAgent(t, ctx, currentFuncName, tc.overrideTokenEnv)
defer cleanup()
out, err = d.Command(ctx, "exit 0").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)
}

const maxTimeout = 30 * time.Second

if !tc.wantAttached {
time.Sleep(maxTimeout)
proCtx, cancel := context.WithTimeout(ctx, maxTimeout)
defer cancel()
attached, err := distroIsProAttached(t, proCtx, d)
require.NoError(t, err, "could not determine if distro is attached")
EduardGomezEscandell marked this conversation as resolved.
Show resolved Hide resolved
require.False(t, attached, "distro should not have been Pro attached")
return
}

require.Eventually(t, func() bool {
attached, err := distroIsProAttached(t, ctx, d)
if err != nil {
t.Logf("could not determine if distro is attached: %v", err)
}
return attached
}, maxTimeout, time.Second, "distro should have been Pro attached")
})
}
}
49 changes: 10 additions & 39 deletions end-to-end/organization_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package endtoend_test

import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
"time"
Expand All @@ -21,6 +19,9 @@ func TestOrganizationProvidedToken(t *testing.T) {
afterDistroRegistration
)

// Let's be lazy and don't fall into the risk of changing the function name without updating the places where its name is used.
currentFuncName := t.Name()

testCases := map[string]struct {
whenToken whenToken

Expand All @@ -41,7 +42,7 @@ func TestOrganizationProvidedToken(t *testing.T) {

if tc.whenToken == beforeDistroRegistration {
activateOrgSubscription(t)
cleanup := startAgent(t, ctx)
cleanup := startAgent(t, ctx, currentFuncName)
defer cleanup()
}

Expand All @@ -59,25 +60,27 @@ func TestOrganizationProvidedToken(t *testing.T) {
require.NoError(t, err, "could not restart distro")

activateOrgSubscription(t)
cleanup := startAgent(t, ctx)
cleanup := startAgent(t, ctx, currentFuncName)
defer cleanup()

out, err := d.Command(ctx, "exit 0").CombinedOutput()
require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out)
}

const maxTimeout = 30 * time.Second
const maxTimeout = 15 * time.Second

if !tc.wantAttached {
time.Sleep(maxTimeout)
attached, err := distroIsProAttached(t, d)
proCtx, cancel := context.WithTimeout(ctx, maxTimeout)
defer cancel()
attached, err := distroIsProAttached(t, proCtx, d)
require.NoError(t, err, "could not determine if distro is attached")
require.False(t, attached, "distro should not have been Pro attached")
return
}

require.Eventually(t, func() bool {
attached, err := distroIsProAttached(t, d)
attached, err := distroIsProAttached(t, ctx, d)
if err != nil {
t.Logf("could not determine if distro is attached: %v", err)
}
Expand All @@ -87,27 +90,6 @@ func TestOrganizationProvidedToken(t *testing.T) {
}
}

func distroIsProAttached(t *testing.T, d wsl.Distro) (bool, error) {
t.Helper()

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

out, err := d.Command(ctx, "pro status --format=json").Output()
if err != nil {
return false, fmt.Errorf("could not call pro status: %v. %s", err, out)
}

var response struct {
Attached bool
}
if err := json.Unmarshal(out, &response); err != nil {
return false, fmt.Errorf("could not parse pro status response: %v: %s", err, out)
}

return response.Attached, nil
}

func activateOrgSubscription(t *testing.T) {
t.Helper()

Expand All @@ -121,14 +103,3 @@ func activateOrgSubscription(t *testing.T) {
err = key.SetStringValue("ProTokenOrg", token)
require.NoError(t, err, "could not write token in registry")
}

//nolint:revive // testing.T must precede the context
func logWslProServiceJournal(t *testing.T, ctx context.Context, d wsl.Distro) {
t.Helper()

out, err := d.Command(ctx, "journalctl --no-pager -u wsl-pro.service").CombinedOutput()
if err != nil {
t.Logf("could not access logs: %v\n%s\n", err, out)
}
t.Logf("wsl-pro-service logs:\n%s\n", out)
}
42 changes: 40 additions & 2 deletions end-to-end/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package endtoend_test
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/canonical/ubuntu-pro-for-windows/common/wsltestutils"
"github.com/stretchr/testify/require"
"github.com/ubuntu/gowsl"
wsl "github.com/ubuntu/gowsl"
)

func testSetup(t *testing.T) {
Expand Down Expand Up @@ -61,10 +63,12 @@ func registerFromTestImage(t *testing.T, ctx context.Context) string {
}

// startAgent starts the GUI (without interacting with it) and waits for the Agent to start.
// A single command line argument is expected. Additionally, environment variable overrides
// in a form of "key=value" strings can be appended to the current environment.
// It stops the agent upon cleanup. If the cleanup fails, the testing will be stopped.
//
//nolint:revive // testing.T must precede the contex
func startAgent(t *testing.T, ctx context.Context) (cleanup func()) {
func startAgent(t *testing.T, ctx context.Context, arg string, environ ...string) (cleanup func()) {
t.Helper()

t.Log("Starting agent")
Expand All @@ -74,12 +78,16 @@ func startAgent(t *testing.T, ctx context.Context) (cleanup func()) {

ubuntupro := filepath.Join(strings.TrimSpace(string(out)), "gui", "ubuntupro.exe")
//nolint:gosec // The executable is located at the Appx directory
cmd := exec.CommandContext(ctx, ubuntupro)
cmd := exec.CommandContext(ctx, ubuntupro, arg)

var buff bytes.Buffer
cmd.Stdout = &buff
cmd.Stderr = &buff

if environ != nil {
cmd.Env = append(cmd.Environ(), environ...)
}

err = cmd.Start()
require.NoError(t, err, "Setup: could not start agent")

Expand Down Expand Up @@ -142,3 +150,33 @@ func stopAgent(ctx context.Context) error {

return fmt.Errorf("could not stop process %q: %v. %s", process, err, out)
}

//nolint:revive // testing.T must precede the context
func distroIsProAttached(t *testing.T, ctx context.Context, d wsl.Distro) (bool, error) {
t.Helper()

out, err := d.Command(ctx, "pro status --format=json").Output()
if err != nil {
return false, fmt.Errorf("could not call pro status: %v. %s", err, out)
}

var response struct {
Attached bool
}
if err := json.Unmarshal(out, &response); err != nil {
return false, fmt.Errorf("could not parse pro status response: %v: %s", err, out)
}

return response.Attached, nil
}

//nolint:revive // testing.T must precede the context
func logWslProServiceJournal(t *testing.T, ctx context.Context, d wsl.Distro) {
t.Helper()

out, err := d.Command(ctx, "journalctl -b --no-pager -u wsl-pro.service").CombinedOutput()
if err != nil {
t.Logf("could not access logs: %v\n%s\n", err, out)
}
t.Logf("wsl-pro-service logs:\n%s\n", out)
}
Loading
Loading