Fix the TabTests! (#3833)

## Summary of the Pull Request

Fix the `TabTests`, and enable testing of types with XAML content. The `TabTests` were written many, many moons ago. they were intended to be our tests of XAML-like content within the Terminal app, so we could have unittests of Tabs, Panes, etc. Between their initial authoring and the day they were checked in, we had a bunch of build changes come in and break them irreperably. 

We've gotten them fixed now with _one weird trick_ <sup>doctors hate me</sup>. As long as there isn't an `App.xbf` in the test's output directory, then the tests will deploy just fine.

We also needed a bit of magic, cribbed straight from TAEF, to enable running test code synchronously on the UI thread. Hence, `CppwinrtTailored.h`.

## References

## PR Checklist
* [x] Closes #2472
* [x] I work here
* [x] Tests added/passed - you better believe it
* [n/a] Requires documentation to be updated

## Validation Steps Performed

![image](https://user-images.githubusercontent.com/18356694/70185192-ef1d0b00-16ae-11ea-8799-b77061e3cdb0.png)
This commit is contained in:
Mike Griese 2019-12-06 14:45:08 -06:00 committed by msftbot[bot]
parent fcd210ce00
commit 9145903e11
16 changed files with 208 additions and 66 deletions

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
@ -15,8 +15,10 @@ using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab
// machine available that can run Windows version 18362.
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ColorSchemeTests : public JsonTestClass
{

View file

@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Module Name:
// - CppWinrtTailored.h
//
// Abstract:
// - This is effectively a copy-paste of TAEF's Tailored.h, ported to cppwinrt.
// Unfortunately, TAEF only provides a CX or pure c++ version of the
// RunOnUIThread function, which doesn't compile for us. This version has been
// ported to cppwinrt for our uses.
// - RunOnUIThread is a helper function for running test code on the UI thread
//
// Author:
// - Mike Griese (zadjii-msft) 04-Dec-2019
#pragma once
#include "pch.h"
extern "C" __declspec(dllimport) HRESULT __stdcall Thread_Wait_For(HANDLE handle, unsigned long milliseconds);
namespace details
{
class Event
{
public:
Event() :
m_handle(::CreateEvent(nullptr, FALSE, FALSE, nullptr))
{
}
~Event()
{
if (IsValid())
{
::CloseHandle(m_handle);
}
}
void Set()
{
::SetEvent(m_handle);
}
HRESULT Wait()
{
return Thread_Wait_For(m_handle, INFINITE);
}
bool IsValid()
{
return m_handle != nullptr;
}
HANDLE m_handle;
};
};
// Function Description:
// - This is a helper function for running a bit of test code on the UI thread.
// It will synchonously dispatch the provided function to the UI thread, and
// wait for that function to complete, before returning to the caller. Callers
// should make sure to VERIFY_SUCCEEDED the result of this function, to ensure
// the code executed successfully.
// Arguments:
// - function: A pointer to some function to run. This should accept no params,
// and any return value will be ignored.
// Return Value:
// - S_OK _after_ we successfully execute the provided function, or another
// HRESULT indicating failure.
template<typename TFunction>
HRESULT RunOnUIThread(const TFunction& function)
{
auto m = winrt::Windows::ApplicationModel::Core::CoreApplication::MainView();
auto cw = m.CoreWindow();
auto d = cw.Dispatcher();
// Create an event so we can wait for the callback to complete
details::Event completedEvent;
if (!completedEvent.IsValid())
{
return HRESULT_FROM_WIN32(::GetLastError());
}
HRESULT invokeResult = E_FAIL;
auto asyncAction = d.RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[&invokeResult, &function]() {
invokeResult = WEX::SafeInvoke([&]() -> bool { function(); return true; });
});
asyncAction.Completed([&completedEvent](auto&&, auto&&) {
completedEvent.Set();
return S_OK;
});
// Wait for the callback to complete
HRESULT hr = completedEvent.Wait();
if (FAILED(hr))
{
return hr;
}
return invokeResult;
}

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
@ -17,8 +17,10 @@ using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab
// machine available that can run Windows version 18362.
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class KeyBindingsTests : public JsonTestClass
{
@ -53,8 +55,8 @@ namespace TerminalAppLocalTests
// - kc: The key chord to look up the bound ActionAndArgs for.
// Return Value:
// - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it.
static const ActionAndArgs KeyBindingsTests::GetActionAndArgs(const implementation::AppKeyBindings& bindings,
const KeyChord& kc)
static const ActionAndArgs GetActionAndArgs(const implementation::AppKeyBindings& bindings,
const KeyChord& kc)
{
std::wstring buffer{ L"" };
if (WI_IsFlagSet(kc.Modifiers(), KeyModifiers::Ctrl))

View file

@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
@ -15,8 +15,10 @@ using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab
// machine available that can run Windows version 18362.
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class ProfileTests : public JsonTestClass
{

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/CascadiaSettings.h"
@ -16,8 +16,10 @@ using namespace WEX::Common;
namespace TerminalAppLocalTests
{
// Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab
// machine available that can run Windows version 18362.
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class SettingsTests : public JsonTestClass
{

View file

@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"
#include "../TerminalApp/ColorScheme.h"
#include "../TerminalApp/Tab.h"
#include "../CppWinrtTailored.h"
using namespace Microsoft::Console;
using namespace TerminalApp;
@ -13,8 +14,10 @@ using namespace WEX::TestExecution;
namespace TerminalAppLocalTests
{
// Unfortunately, these tests _WILL NOT_ work in our CI, until we have a lab
// machine available that can run Windows version 18362.
// TODO:microsoft/terminal#3838:
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
// an updated TAEF that will let us install framework packages when the test
// package is deployed. Until then, these tests won't deploy in CI.
class TabTests
{
@ -24,13 +27,16 @@ namespace TerminalAppLocalTests
// Islands to host our UI. However, in these tests, we don't really need
// to run full trust - we just need to get some UI elements created. So
// we can just rely on the normal UWP activation to create us.
// UNFORTUNATELY, this doesn't seem to work yet, and the tests fail when
// instantiating XAML elements
//
// IMPORTANTLY! When tests need to make XAML objects, or do XAML things,
// make sure to use RunOnUIThread. This helper will dispatch a lambda to
// be run on the UI thread.
BEGIN_TEST_CLASS(TabTests)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:WaitForXamlWindowActivation", L"true")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TerminalApp.LocalTests.AppxManifest.xml")
TEST_CLASS_PROPERTY(L"UAP:Host", L"Xaml")
TEST_CLASS_PROPERTY(L"UAP:WaitForXamlWindowActivation", L"true")
END_TEST_CLASS()
// These four tests act as canary tests. If one of them fails, then they
@ -64,32 +70,53 @@ namespace TerminalAppLocalTests
void TabTests::TryCreateXamlObjects()
{
// Verify we can create a some XAML objects
// Just creating all of them is enough to know that everything is working.
winrt::Windows::UI::Xaml::Controls::UserControl controlRoot;
VERIFY_IS_NOT_NULL(controlRoot);
winrt::Windows::UI::Xaml::Controls::Grid root;
VERIFY_IS_NOT_NULL(root);
winrt::Windows::UI::Xaml::Controls::SwapChainPanel swapChainPanel;
VERIFY_IS_NOT_NULL(swapChainPanel);
winrt::Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollBar;
VERIFY_IS_NOT_NULL(scrollBar);
auto result = RunOnUIThread([]() {
VERIFY_IS_TRUE(true, L"Congrats! We're running on the UI thread!");
auto v = winrt::Windows::ApplicationModel::Core::CoreApplication::GetCurrentView();
VERIFY_IS_NOT_NULL(v, L"Ensure we have a current view");
// Verify we can create a some XAML objects
// Just creating all of them is enough to know that everything is working.
winrt::Windows::UI::Xaml::Controls::UserControl controlRoot;
VERIFY_IS_NOT_NULL(controlRoot, L"Try making a UserControl");
winrt::Windows::UI::Xaml::Controls::Grid root;
VERIFY_IS_NOT_NULL(root, L"Try making a Grid");
winrt::Windows::UI::Xaml::Controls::SwapChainPanel swapChainPanel;
VERIFY_IS_NOT_NULL(swapChainPanel, L"Try making a SwapChainPanel");
winrt::Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollBar;
VERIFY_IS_NOT_NULL(scrollBar, L"Try making a ScrollBar");
});
VERIFY_SUCCEEDED(result);
}
void TabTests::TryCreateTab()
{
// Just try creating all of:
// 1. one of our pure c++ types (Profile)
// 2. one of our c++winrt types (TermControl)
// 3. one of our types that uses MUX/Xaml (Tab).
// Just creating all of them is enough to know that everything is working.
const auto profileGuid{ Utils::CreateGuid() };
winrt::Microsoft::Terminal::TerminalControl::TermControl term{};
VERIFY_IS_NOT_NULL(term);
// If you leave the Tab shared_ptr owned by the RunOnUIThread lambda, it
// will crash when the test tears down. Not totally clear why, but make
// sure it's owned outside the lambda
std::shared_ptr<Tab> newTab{ nullptr };
auto newTab = std::make_shared<Tab>(profileGuid, term);
auto result = RunOnUIThread([&newTab]() {
// Try creating all of:
// 1. one of our pure c++ types (Profile)
// 2. one of our c++winrt types (TerminalSettings, EchoConnection)
// 3. one of our types that uses MUX/Xaml (TermControl).
// 4. one of our types that uses MUX/Xaml in this dll (Tab).
// Just creating all of them is enough to know that everything is working.
const auto profileGuid{ Utils::CreateGuid() };
winrt::Microsoft::Terminal::Settings::TerminalSettings settings{};
VERIFY_IS_NOT_NULL(settings);
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
VERIFY_IS_NOT_NULL(conn);
winrt::Microsoft::Terminal::TerminalControl::TermControl term{ settings, conn };
VERIFY_IS_NOT_NULL(term);
VERIFY_IS_NOT_NULL(newTab);
newTab = std::make_shared<Tab>(profileGuid, term);
VERIFY_IS_NOT_NULL(newTab);
});
VERIFY_SUCCEEDED(result);
}
}

View file

@ -28,7 +28,7 @@
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.18362.0" MaxVersionTested="10.0.18362.0" />
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.18362.0" />
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug" MinVersion="14.0.27023.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
<PackageDependency Name="Microsoft.VCLibs.140.00.Debug.UWPDesktop" MinVersion="14.0.27027.1" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
</Dependencies>

View file

@ -25,12 +25,13 @@
</PropertyGroup>
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)\src\common.build.pre.props" />
<Import Project="$(OpenConsoleDir)\src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="precomp.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="JsonTestClass.h" />
<ClInclude Include="CppWinrtTailored.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
@ -40,7 +41,7 @@
<ClCompile Include="ColorSchemeTests.cpp" />
<ClCompile Include="KeyBindingsTests.cpp" />
<ClCompile Include="TabTests.cpp" />
<ClCompile Include="precomp.cpp">
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
@ -69,13 +70,13 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>onecoreuap.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
@ -166,7 +167,8 @@
</MSBuild>
<ItemGroup>
<MyPackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" Condition="'%(Extension)'=='.dll' Or '%(Extension)'=='.pri' Or '%(Extension)'=='.xbf'" />
<!-- IMPORTANT: Grab all the xbf files _except_ App.xbf. If that's there, then the tests will hyperexplode. -->
<MyPackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" Condition="'%(Extension)'=='.dll' Or '%(Extension)'=='.pri' Or ('%(Extension)'=='.xbf' And '%(Filename)' != 'App')" />
</ItemGroup>
</Target>

View file

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "pch.h"

View file

@ -25,22 +25,21 @@ Author(s):
#undef GetCurrentTime
#endif
#include <wil/cppwinrt.h>
#include <unknwn.h>
#include <hstring.h>
#include <WexTestClass.h>
#include <json.h>
#include "consoletaeftemplates.hpp"
// Needed just for XamlIslands to work at all:
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
// Common includes for most tests:
#include "../../inc/argb.h"
#include "../../inc/conattrs.hpp"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/windows.ui.core.h>
@ -51,3 +50,9 @@ Author(s):
#include <winrt/Windows.ui.xaml.input.h>
#include <windows.ui.xaml.media.dxinterop.h>
#include <winrt/windows.applicationmodel.core.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>

View file

@ -6,8 +6,6 @@
#include "Profile.h"
#include "CascadiaSettings.h"
#include "winrt/Microsoft.Terminal.TerminalConnection.h"
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
@ -1130,7 +1128,7 @@ void Pane::_SetupResources()
}
const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground");
if (res.HasKey(accentColorKey))
if (res.HasKey(tabViewBackgroundKey))
{
winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey);
s_unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();

View file

@ -2,7 +2,6 @@
// Licensed under the MIT license.
#pragma once
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include "Pane.h"
class Tab : public std::enable_shared_from_this<Tab>

View file

@ -3,8 +3,6 @@
#pragma once
#include "winrt/Microsoft.UI.Xaml.Controls.h"
#include "TerminalPage.g.h"
#include "Tab.h"
#include "CascadiaSettings.h"
@ -12,10 +10,7 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
namespace winrt::TerminalApp::implementation
{

View file

@ -38,6 +38,9 @@
#include "winrt/Windows.UI.Xaml.Documents.h"
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include <windows.ui.xaml.media.dxinterop.h>
@ -55,3 +58,5 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <shellapi.h>
#include <filesystem>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>

View file

@ -42,12 +42,9 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
{
rows;
columns;
throw hresult_not_implemented();
}
void EchoConnection::Close()
{
throw hresult_not_implemented();
}
}