From d2b587e06e9429676c2ac18cdabe2b5505d1a455 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:55:10 -0300 Subject: [PATCH 01/31] Allows selecting the Flutter entry point If unset, remains the default entry point (lib/main.dart). If the MSBuild property UP4W_END_TO_END is set to anything, then end_to_end/end_to_end_test.dart becomes the entry point. --- .github/workflows/qa-azure.yaml | 3 ++- msix/gui/gui.targets | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 9725c660d..5737380df 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -64,7 +64,7 @@ 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: | + run: | msbuild ` .\msix\msix.sln ` -target:Build ` @@ -75,6 +75,7 @@ jobs: -property:AppxBundlePlatforms=x64 ` -property:UapAppxPackageBuildMode=SideloadOnly ` -property:Version=0.0.1+${{ github.sha }} ` + -property:UP4W_END_TO_END=true ` -nologo ` -verbosity:normal - name: Upload sideload artifact diff --git a/msix/gui/gui.targets b/msix/gui/gui.targets index 7293305cf..adb37fd54 100644 --- a/msix/gui/gui.targets +++ b/msix/gui/gui.targets @@ -8,6 +8,7 @@ $(MSBuildThisFileDirectory)\icon.ico $(Configuration.ToLower()) + -t end_to_end/end_to_end_test.dart @@ -17,7 +18,7 @@ - + From 373f1c98afc32bfc272771e60a839b5a8eef57e4 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:09:49 -0300 Subject: [PATCH 02/31] Allows building the msix in Debug mode Otherwise some pieces of flutter automation might misbehave. The resulting would not be installable, though. We need to install first the Debug version of VCLibs. That comes with Visual Studio, so we just need to know its path. --- .github/workflows/qa-azure.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 5737380df..52c07c954 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -70,7 +70,7 @@ jobs: -target:Build ` -maxCpuCount ` -nodeReuse:false ` - -property:Configuration=Release ` + -property:Configuration=Debug ` -property:AppxBundle=Always ` -property:AppxBundlePlatforms=x64 ` -property:UapAppxPackageBuildMode=SideloadOnly ` @@ -145,6 +145,9 @@ jobs: $cert = "$(Get-ChildItem windows-agent/UbuntuProForWindows_*.cer)" Import-Certificate -FilePath "${cert}" -CertStoreLocation Cert:LocalMachine\TrustedPeople + # Installing a debug version of VCLibs from the SDK is required, otherwise installing the Ubuntu pro debug appx will fail. + Add-AppxPackage "$env:ExtensionSdkDir\Microsoft.VCLibs.Desktop\14.0\Appx\Debug\x64\Microsoft.VCLibs.x64.Debug.14.00.Desktop.appx" + Write-Output "::endgroup::" Write-Output "::group::Set up WSL Pro Service" From 02e25eb7454f5f5b2d4edbc8fc013d23b9ba7330 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 29 Sep 2023 15:45:34 -0300 Subject: [PATCH 03/31] Sync stdio if attaching to a parent console app By default, the Flutter main.cpp: 1. creates a new console if parent is a debugger. 2. or attaches to the parent console. It does (2) assuming the parent is the flutter tool (flutter run or flutter test - for example). If its not, no console output would be visible. A DeviceDesktopLoggerReader is be required to collect the std outputs. This change adds detection for the case when the parent is a console shell Thus, not the flutter tool. We want to see the console output in that case. It could well be the end-to-end test. ;) --- .../ubuntupro/windows/runner/main.cpp | 4 +-- .../ubuntupro/windows/runner/utils.cpp | 30 +++++++++++++++++++ gui/packages/ubuntupro/windows/runner/utils.h | 14 +++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/gui/packages/ubuntupro/windows/runner/main.cpp b/gui/packages/ubuntupro/windows/runner/main.cpp index ba47ab5c6..763740ddc 100644 --- a/gui/packages/ubuntupro/windows/runner/main.cpp +++ b/gui/packages/ubuntupro/windows/runner/main.cpp @@ -9,9 +9,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } + SetupConsole(); // Initialize COM, so that it is available for use in the library and/or // plugins. diff --git a/gui/packages/ubuntupro/windows/runner/utils.cpp b/gui/packages/ubuntupro/windows/runner/utils.cpp index 1e5f5ab33..a154b9b4c 100644 --- a/gui/packages/ubuntupro/windows/runner/utils.cpp +++ b/gui/packages/ubuntupro/windows/runner/utils.cpp @@ -5,6 +5,7 @@ #include #include +#include #include void CreateAndAttachConsole() { @@ -21,6 +22,35 @@ void CreateAndAttachConsole() { } } +void SetupConsole() { + // Only succeeds if the parent process is a console app. + if (::AttachConsole(ATTACH_PARENT_PROCESS)) { + // In which case we want to know whether the parent is the flutter tool or a + // CLI shell. + std::array flutterSwitchesEnvVar{}; + // What matters is the fact that the env var exists, it's value is + // irrelevant for this case. + // https://github.com/flutter/flutter/blob/cfdaf1e593cf0b012bc8ff5a9c1e780ad5fbc153/packages/flutter_tools/lib/src/desktop_device.dart#L217 + if (0 == GetEnvironmentVariableW( + L"FLUTTER_ENGINE_SWITCHES", &flutterSwitchesEnvVar[0], + // I'm sure the value 4 fits in a DWORD. + static_cast(flutterSwitchesEnvVar.size())) && + GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + // Not running by the flutter tool. OK to resync stdio. + FlutterDesktopResyncOutputStreams(); + // More about the desktop device log reader inside the Flutter tool: + // https://github.com/flutter/flutter/blob/cfdaf1e593cf0b012bc8ff5a9c1e780ad5fbc153/packages/flutter_tools/lib/src/desktop_device.dart#L310 + } + // If the parent is not a console app, it could be an IDE, thus check for + // the presence of a debugger. Otherwise forget about the console. + // This is a GUI application after all. + } else { + if (::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + } +} + std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; diff --git a/gui/packages/ubuntupro/windows/runner/utils.h b/gui/packages/ubuntupro/windows/runner/utils.h index 3879d5475..2feb847bf 100644 --- a/gui/packages/ubuntupro/windows/runner/utils.h +++ b/gui/packages/ubuntupro/windows/runner/utils.h @@ -8,6 +8,20 @@ // it for both the runner and the Flutter library. void CreateAndAttachConsole(); +// Conditionally arranges the console output so that we preserve the default behavior when started by the flutter tool +// or by a debugger, add add a new behavior for when started by a console shell: resync stdio so the outputs are +// visible in the parent console. Useful for end-to-end tests (as well as for apps intended to be started by both a +// desktop and console shells). +// +// In a nutshell: +// 1. If started by the Flutter tool (which is via CLI), it attaches to the parent console and redirects its output so +// the desktop device log reader can consume its outputs. +// 2. If started by a debugger (which is usually not via CLI on Windows), i.e. creates a new console and and redirects +// its output so the desktop device log reader can consume its outputs. +// 3. If started by a shell (console, but not the flutter tool), attaches to the parent console and resync stdio so the +// outputs are visible in the parent console, since there is no log reader in this context. +void SetupConsole(); + // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); From 851741f8f156cc6d9f4b825630f78f30e78bc189 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:57:10 -0300 Subject: [PATCH 04/31] FlutterWindow to be notified of test completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combined with the console setup allows compiling a self driven app independent of the `flutter test´ tool, that can close the window once tests finish. This way we can prescript how the GUI must behave, build a package, deploy, run and assert the side effects in the end-to-end tests. --- .../windows/runner/flutter_window.cpp | 26 +++++++++++++++++++ .../ubuntupro/windows/runner/flutter_window.h | 9 +++++++ 2 files changed, 35 insertions(+) diff --git a/gui/packages/ubuntupro/windows/runner/flutter_window.cpp b/gui/packages/ubuntupro/windows/runner/flutter_window.cpp index 1575df164..378633b17 100644 --- a/gui/packages/ubuntupro/windows/runner/flutter_window.cpp +++ b/gui/packages/ubuntupro/windows/runner/flutter_window.cpp @@ -1,6 +1,9 @@ #include "flutter_window.h" +#include + #include +#include #include "flutter/generated_plugin_registrant.h" @@ -30,9 +33,32 @@ bool FlutterWindow::OnCreate() { flutter_controller_->engine()->SetNextFrameCallback([&]() { this->Show(); }); + integrationTestChannel = std::make_unique< + flutter::MethodChannel>( + flutter_controller_->engine()->messenger(), + // https://github.com/flutter/flutter/blob/master/packages/integration_test/lib/src/channel.dart#L9 + "plugins.flutter.io/integration_test", + &flutter::StandardMethodCodec::GetInstance()); + + integrationTestChannel->SetMethodCallHandler( + [this](auto const& call, auto result) { + HandleMethodCall(call, std::move(result)); + }); + return true; } +void FlutterWindow::HandleMethodCall( + flutter::MethodCall const& call, + std::unique_ptr> result) { + // https://github.com/flutter/flutter/blob/master/packages/integration_test/lib/integration_test.dart#L55-L63 + if (call.method_name().compare("allTestsFinished") == 0) { + result->Success(); + ::PostMessage(GetHandle(), WM_CLOSE, 0, 0); + return; + } +} + void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; diff --git a/gui/packages/ubuntupro/windows/runner/flutter_window.h b/gui/packages/ubuntupro/windows/runner/flutter_window.h index 6da0652f0..e8cd77cab 100644 --- a/gui/packages/ubuntupro/windows/runner/flutter_window.h +++ b/gui/packages/ubuntupro/windows/runner/flutter_window.h @@ -3,6 +3,7 @@ #include #include +#include #include @@ -28,6 +29,14 @@ class FlutterWindow : public Win32Window { // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; + + // The integration test method channel (my backdoor) + std::unique_ptr> + integrationTestChannel; + + void HandleMethodCall( + flutter::MethodCall const& call, + std::unique_ptr> result); }; #endif // RUNNER_FLUTTER_WINDOW_H_ From ed38b26f0ecb9025a8a94420f551e4d054229bcd Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:59:28 -0300 Subject: [PATCH 05/31] Implements the Flutter automation for e2e Assertions should be minimal. Only one test scenario must be active for the lifetime of the process. The CLI arg[0] determines what scenario to activate. That matches the test function name on the Go side. There is where the assertions must exist. There we assert the side effects on registry, distros etc. This is merely a script (compiled to machine code, ofc) --- .../ubuntupro/end_to_end/end_to_end_test.dart | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 gui/packages/ubuntupro/end_to_end/end_to_end_test.dart diff --git a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart new file mode 100644 index 000000000..e6feda9ec --- /dev/null +++ b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart @@ -0,0 +1,89 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:stack_trace/stack_trace.dart' as stack_trace; +import 'package:ubuntupro/core/environment.dart'; +import 'package:ubuntupro/main.dart' as app; +import 'package:ubuntupro/pages/subscription_status/subscribe_now_page.dart'; +import 'package:ubuntupro/pages/subscription_status/subscription_status_page.dart'; + +import '../test/utils/l10n_tester.dart'; + +const proTokenEnv = 'UP4W_TEST_PRO_TOKEN'; + +typedef TestCases = Map Function(WidgetTester)>; + +void main(List args) { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + FlutterError.demangleStackTrace = (stack) { + if (stack is stack_trace.Trace) return stack.vmTrace; + if (stack is stack_trace.Chain) return stack.toTrace().vmTrace; + return stack; + }; + + const testCases = { + 'TestOrganizationProvidedToken': testOrganizationProvidedToken, + 'TestManualTokenInput': testManualTokenInput, + }; + + final scenario = args[0]; + + if (!testCases.keys.contains(scenario)) { + debugPrint('"$scenario" is not a valid test scenario.'); + exit(1); + } + + // For a single run of the end-to-end tests we can only have one active GUI test scenario, + // what implies that we must define a single 'testWidgets'. The actual function body will be determined at runtime. + testWidgets( + scenario, + testCases[scenario]!, + skip: !Platform.isWindows, + ); +} + +Future testOrganizationProvidedToken(WidgetTester tester) async { + await app.main(); + await tester.pumpAndSettle(); + + // asserts that we transitioned to the organization-managed status page. + final l10n = tester.l10n(); + expect(find.text(l10n.orgManaged), findsOneWidget); +} + +Future testManualTokenInput(WidgetTester tester) async { + await app.main(); + await tester.pumpAndSettle(); + + // The "subscribe now page" is only shown if the GUI communicates with the background agent. + var l10n = tester.l10n(); + + // expands the collapsed input field group + final toggle = find.byType(IconButton); + await tester.tap(toggle); + await tester.pumpAndSettle(); + + // finds the pro token from the environment + final goodToken = Environment()[proTokenEnv]; + expect( + goodToken, + isNotNull, + reason: '$proTokenEnv must be set to a valid token.', + ); + + // enters a good token value + final inputField = find.byType(TextField); + await tester.enterText(inputField, goodToken!); + await tester.pumpAndSettle(); + + // submits the input. + final button = find.text(l10n.apply); + await tester.tap(button); + await tester.pumpAndSettle(); + + // asserts that we transitioned to the user-managed status page. + l10n = tester.l10n(); + expect(find.text(l10n.manuallyManaged), findsOneWidget); +} From b7956cbcc1531bacb692cdac75e44574c54248b6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 02:00:09 -0300 Subject: [PATCH 06/31] Moves some test utilities for sharing I'll need those in the manual token case --- end-to-end/organization_token_test.go | 34 --------------------------- end-to-end/utils_test.go | 34 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index dbb7e9cc2..ade910f97 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -2,8 +2,6 @@ package endtoend_test import ( "context" - "encoding/json" - "fmt" "os" "testing" "time" @@ -87,27 +85,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() @@ -121,14 +98,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) -} diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index a87a94cc4..19f3b5461 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -3,6 +3,7 @@ package endtoend_test import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io/fs" @@ -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) { @@ -142,3 +144,35 @@ func stopAgent(ctx context.Context) error { return fmt.Errorf("could not stop process %q: %v. %s", process, err, out) } + +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 +} + +//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) +} From fbe54977c898933bde6868bb7156c0f1ae4670f9 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:17:32 -0300 Subject: [PATCH 07/31] Implements getCurrentFuncName To allow lazy consistency. The function name is the argument the GUI expects to figure out which scenario to activate. --- end-to-end/utils_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index 19f3b5461..3e3877f7d 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "testing" "time" @@ -176,3 +177,13 @@ func logWslProServiceJournal(t *testing.T, ctx context.Context, d wsl.Distro) { } t.Logf("wsl-pro-service logs:\n%s\n", out) } + +// getCurrentFuncName will return the caller function's name. +func getCurrentFuncName() string { + pc, _, _, _ := runtime.Caller(1) + // fullname could be for example "github.com/canonical/ubuntu-pro-for-windows/end-to-end_test.TestManualTokenInput" + fullname := runtime.FuncForPC(pc).Name() + lastDotIndex := strings.LastIndex(fullname, ".") + // if not found, return the whole fullname (lastDotIndex==-1) + return fullname[lastDotIndex+1:] +} From 92628a0fe5b0660bbec6cd9e47467da51a660cae Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:18:33 -0300 Subject: [PATCH 08/31] Allows CLI args and env overrides to startAgent --- end-to-end/utils_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index 3e3877f7d..bad26fb41 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -64,10 +64,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") @@ -77,12 +79,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") From 15c14e213e61325fe1dd3646083fbc0a99b8ce77 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 01:18:57 -0300 Subject: [PATCH 09/31] Updates org token test to use the CLI arg --- end-to-end/organization_token_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index ade910f97..3ecb2f259 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -19,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 := getCurrentFuncName() + testCases := map[string]struct { whenToken whenToken @@ -39,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() } @@ -57,7 +60,7 @@ 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() From 87226c049cfd04b0489028af1de21ad9193363e2 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 02:01:19 -0300 Subject: [PATCH 10/31] Implements the manual token input test case It starts the GUI in the manual token input scenario The GUI must then apply the token obtained from the environment This asserts that previously and newly registered distros are affected --- end-to-end/manual_token_input_test.go | 87 +++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 end-to-end/manual_token_input_test.go diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go new file mode 100644 index 000000000..e12711a85 --- /dev/null +++ b/end-to-end/manual_token_input_test.go @@ -0,0 +1,87 @@ +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 := getCurrentFuncName() + + 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 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 { + // the exact time between registering the distro and running the app cannot be determined, so there is a chance + // of this starting the app before registration completes. + time.Sleep(5 * time.Second) + cleanup := startAgent(t, ctx, currentFuncName, tc.overrideTokenEnv) + defer cleanup() + } + + time.Sleep(5 * time.Second) + out, err = d.Command(ctx, "exit 0").CombinedOutput() + require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) + + if !tc.wantAttached { + attached, err := distroIsProAttached(t, 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) + if err != nil { + t.Logf("could not determine if distro is attached: %v", err) + } + return attached + }, 30*time.Second, time.Second, "distro should have been Pro attached") + }) + } +} From a2c9191188cd7d549a4aec8fc4551c3187408c72 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 09:23:46 -0300 Subject: [PATCH 11/31] Corrects the path of the built artifacts since now they have the "_Debug" suffix --- .github/workflows/qa-azure.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 52c07c954..9da528315 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -83,8 +83,8 @@ jobs: with: name: UbuntuProForWindows+${{ github.sha }} path: | - msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64.cer - msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64.msixbundle + msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64_Debug.cer + msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64_Debug.msixbundle vm-setup: name: Set up Azure VM From 54e7aae12ae65e9f9745dda9ab2c849bbbae0cee Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 4 Oct 2023 09:42:54 -0300 Subject: [PATCH 12/31] Hard coded pat to VCLibs Debug appx in the workflow file. Cannot rely on $env:ExtensionSdkDir --- .github/workflows/qa-azure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 9da528315..404651059 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -146,7 +146,7 @@ jobs: Import-Certificate -FilePath "${cert}" -CertStoreLocation Cert:LocalMachine\TrustedPeople # Installing a debug version of VCLibs from the SDK is required, otherwise installing the Ubuntu pro debug appx will fail. - Add-AppxPackage "$env:ExtensionSdkDir\Microsoft.VCLibs.Desktop\14.0\Appx\Debug\x64\Microsoft.VCLibs.x64.Debug.14.00.Desktop.appx" + 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" Write-Output "::endgroup::" From 0b32a400b4eb1a01596c8fb4ca9df90f88d2ba4d Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 5 Oct 2023 22:48:24 -0300 Subject: [PATCH 13/31] getCurrentFuncName() not needed due t.Name() t.Name() at the start of the test function provides the function name. --- end-to-end/manual_token_input_test.go | 2 +- end-to-end/organization_token_test.go | 2 +- end-to-end/utils_test.go | 11 ----------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index e12711a85..69dcaab84 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -19,7 +19,7 @@ func TestManualTokenInput(t *testing.T) { ) // 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 := getCurrentFuncName() + currentFuncName := t.Name() testCases := map[string]struct { whenToken whenToken diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index 3ecb2f259..6b81841b9 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -20,7 +20,7 @@ func TestOrganizationProvidedToken(t *testing.T) { ) // 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 := getCurrentFuncName() + currentFuncName := t.Name() testCases := map[string]struct { whenToken whenToken diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index bad26fb41..de1b3118b 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "strings" "testing" "time" @@ -183,13 +182,3 @@ func logWslProServiceJournal(t *testing.T, ctx context.Context, d wsl.Distro) { } t.Logf("wsl-pro-service logs:\n%s\n", out) } - -// getCurrentFuncName will return the caller function's name. -func getCurrentFuncName() string { - pc, _, _, _ := runtime.Caller(1) - // fullname could be for example "github.com/canonical/ubuntu-pro-for-windows/end-to-end_test.TestManualTokenInput" - fullname := runtime.FuncForPC(pc).Name() - lastDotIndex := strings.LastIndex(fullname, ".") - // if not found, return the whole fullname (lastDotIndex==-1) - return fullname[lastDotIndex+1:] -} From 3ac6d0eb66bf4404a661028d94a3a3171de755d0 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 5 Oct 2023 22:55:16 -0300 Subject: [PATCH 14/31] Ensures the same wait time on both test paths wantAttached and not wantAttached --- end-to-end/manual_token_input_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 69dcaab84..bb6d21312 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -68,7 +68,9 @@ func TestManualTokenInput(t *testing.T) { out, err = d.Command(ctx, "exit 0").CombinedOutput() require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) + maxTimeout := 30 * time.Second if !tc.wantAttached { + time.Sleep(maxTimeout) attached, err := distroIsProAttached(t, d) require.NoError(t, err, "could not determine if distro is attached") require.False(t, attached, "distro should not have been Pro attached") @@ -81,7 +83,7 @@ func TestManualTokenInput(t *testing.T) { t.Logf("could not determine if distro is attached: %v", err) } return attached - }, 30*time.Second, time.Second, "distro should have been Pro attached") + }, maxTimeout, time.Second, "distro should have been Pro attached") }) } } From 45046f7daf09272840daa9306077b11c350fd355 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 5 Oct 2023 23:03:56 -0300 Subject: [PATCH 15/31] Reduces the amount of time sleeping --- end-to-end/manual_token_input_test.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index bb6d21312..0243e2722 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -57,18 +57,13 @@ func TestManualTokenInput(t *testing.T) { // ... or after registration, but never both. if tc.whenToken == afterDistroRegistration { - // the exact time between registering the distro and running the app cannot be determined, so there is a chance - // of this starting the app before registration completes. - time.Sleep(5 * time.Second) 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) } - time.Sleep(5 * time.Second) - out, err = d.Command(ctx, "exit 0").CombinedOutput() - require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) - - maxTimeout := 30 * time.Second + maxTimeout := 15 * time.Second if !tc.wantAttached { time.Sleep(maxTimeout) attached, err := distroIsProAttached(t, d) From 3a2f6b5b068b2250cf5ee6c57eac0f51fd95b209 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 5 Oct 2023 23:10:40 -0300 Subject: [PATCH 16/31] Print journal only if test failed --- end-to-end/manual_token_input_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 0243e2722..287c87eda 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -50,7 +50,11 @@ func TestManualTokenInput(t *testing.T) { name := registerFromTestImage(t, ctx) d := wsl.NewDistro(ctx, name) - defer logWslProServiceJournal(t, ctx, d) + 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) From 339e39b808ecab1adec3c09ca2118a4fa62a3fb5 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 00:05:56 -0300 Subject: [PATCH 17/31] CI now builds both Debug and Release modes in matrix The matrix is currently expressed as either Release (production) or Debug (for end-to-end tests). It uploads artifacts named after the current matrix mode, So when running the tests, we don't download what we don't need. --- .github/workflows/qa-azure.yaml | 37 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 404651059..4b6556c60 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -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: @@ -65,26 +68,27 @@ jobs: 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=Debug ` - -property:AppxBundle=Always ` - -property:AppxBundlePlatforms=x64 ` - -property:UapAppxPackageBuildMode=SideloadOnly ` - -property:Version=0.0.1+${{ github.sha }} ` - -property:UP4W_END_TO_END=true ` - -nologo ` + msbuild ` + .\msix\msix.sln ` + -target:Build ` + -maxCpuCount ` + -nodeReuse:false ` + -property:Configuration=${{ matrix.mode == 'production' && 'Debug' || 'Release'}} ` + -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_Debug.cer - msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*_x64_Debug.msixbundle + msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*.cer + msix/UbuntuProForWindows/AppPackages/UbuntuProForWindows_*/UbuntuProForWindows_*.msixbundle vm-setup: name: Set up Azure VM @@ -128,7 +132,8 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v3 with: - # name: is left blank so that all artifacts are downloaded + # We are not interesting in downloading the production build. + name: UbuntuProForWindows+${{ github.sha }}-end_to_end_tests path: ci-artifacts - name: Set up artifacts shell: powershell From bae0864bc3705b5f5062dd4541d360cdd108f999 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 09:42:13 -0300 Subject: [PATCH 18/31] OverrideEntrypoint property to recognize 'false' Making easier for CI --- msix/gui/gui.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/gui/gui.targets b/msix/gui/gui.targets index adb37fd54..06e48a581 100644 --- a/msix/gui/gui.targets +++ b/msix/gui/gui.targets @@ -8,7 +8,7 @@ $(MSBuildThisFileDirectory)\icon.ico $(Configuration.ToLower()) - -t end_to_end/end_to_end_test.dart + -t end_to_end/end_to_end_test.dart From 003b7ed666f19a5a1e3402e8af5a887e25c38d4d Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 09:44:26 -0300 Subject: [PATCH 19/31] Updates the build Appx tool to support build modes Compatible with the changes in CI --- tools/build/build-appx.ps1 | 46 ++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/tools/build/build-appx.ps1 b/tools/build/build-appx.ps1 index 87cd5c415..11dd2bebb 100644 --- a/tools/build/build-appx.ps1 +++ b/tools/build/build-appx.ps1 @@ -3,6 +3,12 @@ Build the Ubuntu Pro For Windows Appx package for local use. #> +param ( + [Parameter(Mandatory = $true, HelpMessage = "prodution, end_to_end_tests.")] + + [string]$mode +) + function Start-VsDevShell { # Looking for a path like # ${env:ProgramFiles}\Microsoft Visual Studio\$VERSION\$RELEASE\Common7\Tools\Launch-VsDevShell.ps1 @@ -25,7 +31,7 @@ function Start-VsDevShell { continue } - foreach ( $release in "Enterprise","Professional","Community") { + foreach ( $release in "Enterprise", "Professional", "Community") { $devShell = "${vsRoot}\${version}\${release}\Common7\Tools\Launch-VsDevShell.ps1" if (! (Test-Path "${devShell}") ) { continue @@ -33,7 +39,7 @@ function Start-VsDevShell { & "${devShell}" -SkipAutomaticLocation return - } + } } Write-Error "Visual Studio developer powershell could not be found" @@ -57,10 +63,10 @@ function Update-Certificate { # Replacing with local certificate $wapproj = ".\msix\UbuntuProForWindows\UbuntuProForWindows.wapproj" (Get-Content -Path "${wapproj}") ` - -replace ` - ".*", ` - "${certificate_thumbprint}" ` - | Set-Content -Path "${wapproj}" + -replace ` + ".*", ` + "${certificate_thumbprint}" ` + | Set-Content -Path "${wapproj}" } function Install-Appx { @@ -90,20 +96,26 @@ Update-Certificate try { msbuild.exe --version -} catch { +} +catch { Start-VsDevShell } -msbuild.exe ` - .\msix\msix.sln ` - -target:Build ` - -maxCpuCount ` - -property:Configuration=Release ` - -property:AppxBundle=Always ` - -property:AppxBundlePlatforms=x64 ` - -property:ProcessorArchitecture=x64 ` - -property:UapAppxPackageBuildMode=SideloadOnly ` - -nologo ` +If ($mode -eq 'production' -and $null -ne $env:UP4W_TEST_WITH_MS_STORE_MOCK) { + Write-Warning "Building the app in Release mode with UP4W_TEST_WITH_MS_STORE_MOCK env var set may lead to build failure or surprising results. Value is $env:UP4W_TEST_WITH_MS_STORE_MOCK." +} + +msbuild.exe ` + .\msix\msix.sln ` + -target:Build ` + -maxCpuCount ` + -property:Configuration=$(If($mode -eq 'production'){"Release"} Else {"Debug"}) ` + -property:AppxBundle=Always ` + -property:AppxBundlePlatforms=x64 ` + -property:ProcessorArchitecture=x64 ` + -property:UapAppxPackageBuildMode=SideloadOnly ` + -nologo ` + -property:UP4W_END_TO_END=$(If($mode -eq 'end_to_end_tests'){"true"} Else {"false"}) ` -verbosity:normal if (! $?) { exit 1 } From c2fe8053a525c1415b1aa9b6f93bd30901cf41d0 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 09:46:51 -0300 Subject: [PATCH 20/31] Updates installation docs Calling out the fact that there is a production and an end-to-end-test-enabled version of the MSIX. --- doc/02.-Installation.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/02.-Installation.md b/doc/02.-Installation.md index 43028de57..f739c5455 100644 --- a/doc/02.-Installation.md +++ b/doc/02.-Installation.md @@ -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.msixbundle + - WSL-Pro-Service: Wsl-pro-service_….deb + +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. From cfec9cb1e2afd84e6a6c9f6a438bf91f3d555e32 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:01:20 -0300 Subject: [PATCH 21/31] Fix grammar in the download artifacts step comment --- .github/workflows/qa-azure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 4b6556c60..00383a2b6 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -132,7 +132,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v3 with: - # We are not interesting in downloading the production build. + # We are not interested in downloading the production build. name: UbuntuProForWindows+${{ github.sha }}-end_to_end_tests path: ci-artifacts - name: Set up artifacts From 2fce2255697c06935ec94e776537ec1c19058a84 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:15:30 -0300 Subject: [PATCH 22/31] Setup debug vclibs to its own step --- .github/workflows/qa-azure.yaml | 8 +++++--- .vscode/tasks.json | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 00383a2b6..49c6f211c 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -150,9 +150,6 @@ jobs: $cert = "$(Get-ChildItem windows-agent/UbuntuProForWindows_*.cer)" Import-Certificate -FilePath "${cert}" -CertStoreLocation Cert:LocalMachine\TrustedPeople - # Installing a debug version of VCLibs from the SDK is required, otherwise installing the Ubuntu pro debug appx will fail. - 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" - Write-Output "::endgroup::" Write-Output "::group::Set up WSL Pro Service" @@ -160,6 +157,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: diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..4bb467c82 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,34 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build Appx", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build" + }, + "command": "powershell.exe -File ${workspaceFolder}\\tools\\build\\build-appx.ps1" + }, + { + "label": "Build Deb", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build" + }, + "windows": { + "command": "powershell.exe -File ${workspaceFolder}\\tools\\build\\build-deb.ps1" + }, + "linux": { + "command": "bash ${workspaceFolder}/tools/build/build-deb.sh" + } + }, + ] +} \ No newline at end of file From 800bfb3e4a26f6ca6c375bec20735e8f490685a6 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:23:33 -0300 Subject: [PATCH 23/31] Leave download artifacts without the 'name' parameter Finding the name of the deb artifact seems complicated. The production MSIX is not that big. We just need to be careful to not use it in the end-to-end tests. --- .github/workflows/qa-azure.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index 49c6f211c..e7763785a 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -132,8 +132,7 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v3 with: - # We are not interested in downloading the production build. - name: UbuntuProForWindows+${{ github.sha }}-end_to_end_tests + # name: is left blank so that all artifacts are downloaded path: ci-artifacts - name: Set up artifacts shell: powershell @@ -143,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)" From 072b42af5c2effe494203797cc1d0905b6af0eed Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:26:10 -0300 Subject: [PATCH 24/31] Remove .deb and .msix* extensions in install doc --- doc/02.-Installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/02.-Installation.md b/doc/02.-Installation.md index f739c5455..779b8947b 100644 --- a/doc/02.-Installation.md +++ b/doc/02.-Installation.md @@ -14,8 +14,8 @@ 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+...-production.msixbundle - - WSL-Pro-Service: Wsl-pro-service_….deb + - 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. From 61e6ee2d13bc9d3e7f70cf95fc636af057d5eac3 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:32:46 -0300 Subject: [PATCH 25/31] Context as parameter for distroIsProAttached() If timeouts are needed, the caller must set it up accordingly. --- end-to-end/manual_token_input_test.go | 7 ++++--- end-to-end/organization_token_test.go | 6 ++++-- end-to-end/utils_test.go | 6 ++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 287c87eda..41a4c2c92 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -69,15 +69,16 @@ func TestManualTokenInput(t *testing.T) { maxTimeout := 15 * time.Second if !tc.wantAttached { - time.Sleep(maxTimeout) - attached, err := distroIsProAttached(t, d) + proCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + 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) } diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index 6b81841b9..d8c85e4d2 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -71,14 +71,16 @@ func TestOrganizationProvidedToken(t *testing.T) { if !tc.wantAttached { time.Sleep(maxTimeout) - attached, err := distroIsProAttached(t, d) + proCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + 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) } diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index de1b3118b..83865fcaf 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -151,12 +151,10 @@ func stopAgent(ctx context.Context) error { return fmt.Errorf("could not stop process %q: %v. %s", process, err, out) } -func distroIsProAttached(t *testing.T, d wsl.Distro) (bool, error) { +//nolint:revive // testing.T must precede the context +func distroIsProAttached(t *testing.T, ctx context.Context, 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) From a4ecd04bdb79e8b2faa7b2a7cfdbe0ae9dfbdd39 Mon Sep 17 00:00:00 2001 From: Carlos Date: Fri, 6 Oct 2023 11:39:35 -0300 Subject: [PATCH 26/31] Fix Debug vs Release logic in the msix build it was inverted, causing the production to be built in debug --- .github/workflows/qa-azure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-azure.yaml b/.github/workflows/qa-azure.yaml index e7763785a..8dee67d0b 100644 --- a/.github/workflows/qa-azure.yaml +++ b/.github/workflows/qa-azure.yaml @@ -73,7 +73,7 @@ jobs: -target:Build ` -maxCpuCount ` -nodeReuse:false ` - -property:Configuration=${{ matrix.mode == 'production' && 'Debug' || 'Release'}} ` + -property:Configuration=${{ matrix.mode == 'production' && 'Release' || 'Debug'}} ` -property:AppxBundle=Always ` -property:AppxBundlePlatforms=x64 ` -property:UapAppxPackageBuildMode=SideloadOnly ` From 3aa41778e91fa6936df33461cfcee81e05feb5ab Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 9 Oct 2023 09:39:32 -0300 Subject: [PATCH 27/31] Del tasks.json commited by mistake :) --- .vscode/tasks.json | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 4bb467c82..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Build Appx", - "type": "shell", - "options": { - "cwd": "${workspaceFolder}" - }, - "group": { - "kind": "build" - }, - "command": "powershell.exe -File ${workspaceFolder}\\tools\\build\\build-appx.ps1" - }, - { - "label": "Build Deb", - "type": "shell", - "options": { - "cwd": "${workspaceFolder}" - }, - "group": { - "kind": "build" - }, - "windows": { - "command": "powershell.exe -File ${workspaceFolder}\\tools\\build\\build-deb.ps1" - }, - "linux": { - "command": "bash ${workspaceFolder}/tools/build/build-deb.sh" - } - }, - ] -} \ No newline at end of file From f85c2a1c491f7115e00c2b76ee405b0efc972734 Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 9 Oct 2023 10:44:40 -0300 Subject: [PATCH 28/31] Restores time.Sleep in the no-attach case. If we really want to ensure the distro doesn't attach, the check must last as long as the longest possibility in the must-attach case. --- end-to-end/manual_token_input_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 41a4c2c92..120a49865 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -69,6 +69,7 @@ func TestManualTokenInput(t *testing.T) { maxTimeout := 15 * time.Second if !tc.wantAttached { + time.Sleep(maxTimeout) proCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() attached, err := distroIsProAttached(t, proCtx, d) From 9090edd47adcd98bcfd3eccbecbf1a51eaecb202 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 10 Oct 2023 22:53:45 -0300 Subject: [PATCH 29/31] Only logs journal from the current boot --- end-to-end/utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/end-to-end/utils_test.go b/end-to-end/utils_test.go index 83865fcaf..c1ab3b64b 100644 --- a/end-to-end/utils_test.go +++ b/end-to-end/utils_test.go @@ -174,7 +174,7 @@ func distroIsProAttached(t *testing.T, ctx context.Context, d wsl.Distro) (bool, 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() + 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) } From a84f60277618a4c13a9902ef3f6c9c82c1e8f97a Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 9 Oct 2023 14:29:50 -0300 Subject: [PATCH 30/31] Unify 15s timeout in the end of tests Small reduction. Seems enough for CI as well. --- end-to-end/manual_token_input_test.go | 5 +++-- end-to-end/organization_token_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 120a49865..64f0a6d57 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -67,10 +67,11 @@ func TestManualTokenInput(t *testing.T) { require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) } - maxTimeout := 15 * time.Second + const maxTimeout = 15 * time.Second + if !tc.wantAttached { time.Sleep(maxTimeout) - proCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + 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") diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index d8c85e4d2..fb5c86467 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -67,11 +67,11 @@ func TestOrganizationProvidedToken(t *testing.T) { 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) - proCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + 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") From d3941805413a440cb3d19a7be14b02b9ccf25958 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 11 Oct 2023 09:09:54 -0300 Subject: [PATCH 31/31] 30s max timeout CI is not happy with 15s apparently. --- end-to-end/manual_token_input_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 64f0a6d57..ca09ae059 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -67,7 +67,7 @@ func TestManualTokenInput(t *testing.T) { require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) } - const maxTimeout = 15 * time.Second + const maxTimeout = 30 * time.Second if !tc.wantAttached { time.Sleep(maxTimeout)