[PATCH] qmgr/tests: Fix issues with handling of transient errors

Aaron Hill aa1ronham at gmail.com
Sun Oct 25 22:51:25 CDT 2020


When a BITS job is being transferred, it may enter into the state
BG_JOB_STATE_TRANSIENT_ERROR (for example, if the hostname fails to
resolve). Currently, entering this state causes qmgr job tests to fail,
even though it may occur due to temporary network issues out of our
control.

If a job enters BG_JOB_STATE_TRANSIENT_ERROR before the timeout has
elapsed, attempt to resume the job using
IBackgroundCopyJob_Resume. If the job is still in
BG_JOB_STATE_TRANSIENT_ERROR, query BITS for detailed error
information, and print it out.

Additionally, ensure that we are able to transfer files on Windows 10
with a metered connection. By default, BITS will not attempt to transfer
a job on a metered connection, instead failing with
BG_JOB_STATE_TRANSIENT_ERROR. On newer versions of Windows, we can
use IBackgroundCopyJob5 to set the transfer policy, forcing the job to
run even on a metered connection. This allows qmgr job tests to pass on
the testbot Windows 10 VMs, which have metered connections enabled in
order to disable Windows Update.

Setting the job transfer policy requires using APIs from BITS 5.0. Add
the minimal amount of the interface needed for the test.

With this patch, the qmgr job tests now pass on all testbot VMs.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=50048
Signed-off-by: Aaron Hill <aa1ronham at gmail.com>
---
 dlls/qmgr/qmgr_local.idl |   1 +
 dlls/qmgr/tests/job.c    | 117 +++++++++++++++++++++++++++++++++++++--
 include/Makefile.in      |   1 +
 include/bits5_0.idl      |  36 ++++++++++++
 4 files changed, 151 insertions(+), 4 deletions(-)
 create mode 100644 include/bits5_0.idl

diff --git a/dlls/qmgr/qmgr_local.idl b/dlls/qmgr/qmgr_local.idl
index 110d479ab92..48439fc5dac 100644
--- a/dlls/qmgr/qmgr_local.idl
+++ b/dlls/qmgr/qmgr_local.idl
@@ -25,3 +25,4 @@
 #include "bits2_0.idl"
 #include "bits2_5.idl"
 #include "bits3_0.idl"
+#include "bits5_0.idl"
diff --git a/dlls/qmgr/tests/job.c b/dlls/qmgr/tests/job.c
index 53b724eadbf..68d15acaf0a 100644
--- a/dlls/qmgr/tests/job.c
+++ b/dlls/qmgr/tests/job.c
@@ -27,6 +27,7 @@
 #include "initguid.h"
 #include "bits2_0.h"
 #include "bits2_5.h"
+#include "bits5_0.h"
 
 /* Globals used by many tests */
 static WCHAR test_remotePathA[MAX_PATH];
@@ -75,6 +76,8 @@ static void init_paths(void)
 static BOOL setup(void)
 {
     HRESULT hres;
+    IBackgroundCopyJob5* test_job_5;
+    BITS_JOB_PROPERTY_VALUE prop_val;
 
     test_manager = NULL;
     test_job = NULL;
@@ -95,6 +98,25 @@ static BOOL setup(void)
         return FALSE;
     }
 
+    /* The Wine TestBot Windows 10 VMs disable Windows Update by putting
+       the network connection in metered mode (see https://wiki.winehq.org/Wine_TestBot_VMs#Windows_configuration)
+
+       Unfortunately, this will make BITS jobs fail, since the default transfer policy
+       on Windows 10 prevents BITs job from running over a metered network
+
+       To allow these tests in this file to run on the testbot, we
+       set the BITS_JOB_PROPERTY_ID_COST_FLAGS property to BITS_COST_STATE_TRANSFER_ALWAYS,
+       ensuring that BITS will still try to run the job on a metered network */
+    prop_val.Dword = BITS_COST_STATE_TRANSFER_ALWAYS;
+    hres = IBackgroundCopyJob_QueryInterface(test_job, &IID_IBackgroundCopyJob5, (void **)&test_job_5);
+    /* BackgroundCopyJob5 was added in Windows 8, so this may not exist. The metered connection
+       workaround is only applied on Windows 10, so it's fine if this fails. */
+    if (SUCCEEDED(hres)) {
+        hres = IBackgroundCopyJob5_SetProperty(test_job_5, BITS_JOB_PROPERTY_ID_COST_FLAGS, prop_val);
+        ok(hres == S_OK, "Failed to set the cost flags: %08x\n", hres);
+        IBackgroundCopyJob5_Release(test_job_5);
+    }
+
     return TRUE;
 }
 
@@ -333,6 +355,43 @@ static void compareFiles(WCHAR *n1, WCHAR *n2)
     ok(memcmp(b1, b2, s1) == 0, "Files differ in contents\n");
 }
 
+/* Handles a timeout in the BG_JOB_STATE_ERROR or BG_JOB_STATE_TRANSIENT_ERROR state */
+static void handle_job_err(void)
+{
+    HRESULT hres;
+    IBackgroundCopyError *err;
+    BG_ERROR_CONTEXT errContext;
+    HRESULT errCode;
+    LPWSTR contextDesc;
+    LPWSTR errDesc;
+
+    hres = IBackgroundCopyJob_GetError(test_job, &err);
+    if (SUCCEEDED(hres)) {
+        hres = IBackgroundCopyError_GetError(err, &errContext, &errCode);
+        if (SUCCEEDED(hres)) {
+            ok(0, "Got context: %d code: %d\n", errContext, errCode);
+        } else {
+            ok(0, "Failed to get error info: 0x%08x\n", hres);
+        }
+
+        hres = IBackgroundCopyError_GetErrorContextDescription(err, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), &contextDesc);
+        if (SUCCEEDED(hres)) {
+            ok(0, "Got context desc: %s\n", wine_dbgstr_w(contextDesc));
+        } else {
+            ok(0, "Failed to get context desc: 0x%08x\n", hres);
+        }
+
+        hres = IBackgroundCopyError_GetErrorDescription(err, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), &errDesc);
+        if (SUCCEEDED(hres)) {
+            ok(0, "Got error desc: %s\n", wine_dbgstr_w(errDesc));
+        } else {
+            ok(0, "Failed to get error desc: 0x%08x\n", hres);
+        }
+    } else {
+        ok(0, "Failed to get error: 0x%08x\n", hres);
+    }
+}
+
 /* Test a complete transfer for local files */
 static void test_CompleteLocal(void)
 {
@@ -362,13 +421,23 @@ static void test_CompleteLocal(void)
         hres = IBackgroundCopyJob_GetState(test_job, &state);
         ok(hres == S_OK, "IBackgroundCopyJob_GetState\n");
         ok(state == BG_JOB_STATE_QUEUED || state == BG_JOB_STATE_CONNECTING
-           || state == BG_JOB_STATE_TRANSFERRING || state == BG_JOB_STATE_TRANSFERRED,
+           || state == BG_JOB_STATE_TRANSFERRING || state == BG_JOB_STATE_TRANSFERRED
+           || state == BG_JOB_STATE_TRANSIENT_ERROR,
            "Bad state: %d\n", state);
+
+        if (state == BG_JOB_STATE_TRANSIENT_ERROR) {
+            hres = IBackgroundCopyJob_Resume(test_job);
+            ok(hres == S_OK, "IBackgroundCopyJob_Resume\n");
+        }
+
         if (state == BG_JOB_STATE_TRANSFERRED)
             break;
         Sleep(1000);
     }
 
+    if (state == BG_JOB_STATE_ERROR || state == BG_JOB_STATE_TRANSIENT_ERROR)
+        handle_job_err();
+
     ok(i < timeout_sec, "BITS jobs timed out\n");
     hres = IBackgroundCopyJob_Complete(test_job);
     ok(hres == S_OK, "IBackgroundCopyJob_Complete\n");
@@ -430,13 +499,24 @@ static void test_CompleteLocalURL(void)
         hres = IBackgroundCopyJob_GetState(test_job, &state);
         ok(hres == S_OK, "IBackgroundCopyJob_GetState\n");
         ok(state == BG_JOB_STATE_QUEUED || state == BG_JOB_STATE_CONNECTING
-           || state == BG_JOB_STATE_TRANSFERRING || state == BG_JOB_STATE_TRANSFERRED,
+           || state == BG_JOB_STATE_TRANSFERRING || state == BG_JOB_STATE_TRANSFERRED
+           || state == BG_JOB_STATE_TRANSIENT_ERROR,
            "Bad state: %d\n", state);
+
+        if (state == BG_JOB_STATE_TRANSIENT_ERROR) {
+            hres = IBackgroundCopyJob_Resume(test_job);
+            ok(hres == S_OK, "IBackgroundCopyJob_Resume\n");
+        }
+
         if (state == BG_JOB_STATE_TRANSFERRED)
             break;
         Sleep(1000);
     }
 
+    if (state == BG_JOB_STATE_ERROR || state == BG_JOB_STATE_TRANSIENT_ERROR)
+        handle_job_err();
+
+
     ok(i < timeout_sec, "BITS jobs timed out\n");
     hres = IBackgroundCopyJob_Complete(test_job);
     ok(hres == S_OK, "IBackgroundCopyJob_Complete\n");
@@ -572,11 +652,22 @@ static void test_HttpOptions(void)
         ok(state == BG_JOB_STATE_QUEUED ||
            state == BG_JOB_STATE_CONNECTING ||
            state == BG_JOB_STATE_TRANSFERRING ||
-           state == BG_JOB_STATE_TRANSFERRED, "unexpected state: %u\n", state);
+           state == BG_JOB_STATE_TRANSFERRED ||
+           state == BG_JOB_STATE_TRANSIENT_ERROR, "unexpected state: %u\n", state);
+
+        if (state == BG_JOB_STATE_TRANSIENT_ERROR) {
+            hr = IBackgroundCopyJob_Resume(test_job);
+            ok(hr == S_OK, "IBackgroundCopyJob_Resume\n");
+        }
 
         if (state == BG_JOB_STATE_TRANSFERRED) break;
         Sleep(1000);
     }
+
+    if (state == BG_JOB_STATE_ERROR || state == BG_JOB_STATE_TRANSIENT_ERROR)
+        handle_job_err();
+
+
     ok(i < timeout, "BITS job timed out\n");
     if (i < timeout)
     {
@@ -651,10 +742,28 @@ START_TEST(job)
     };
     const test_t *test;
     int i;
+    HRESULT hres;
 
     init_paths();
 
-    CoInitialize(NULL);
+    /* CoInitializeEx and CoInitializeSecurity with RPC_C_IMP_LEVEL_IMPERSONATE
+     * are required to set the job transfer policy
+     * See https://docs.microsoft.com/en-us/windows/win32/bits/how-to-block-a-bits-job-from-downloading-over-an-expensive-connection
+     */
+    hres = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+    if (FAILED(hres)) {
+        ok(0, "CoInitializeEx faied: %0x\n", hres);
+        return;
+    }
+
+    hres = CoInitializeSecurity(NULL, -1, NULL, NULL,
+                           RPC_C_AUTHN_LEVEL_CONNECT,
+                           RPC_C_IMP_LEVEL_IMPERSONATE,
+                           NULL, EOAC_NONE, 0);
+    if (FAILED(hres)) {
+        ok(0, "CoInitializeSecurity failed: %0x\n", hres);
+        return;
+    }
 
     if (FAILED(test_create_manager()))
     {
diff --git a/include/Makefile.in b/include/Makefile.in
index 23306689cab..b2c8ba7f987 100644
--- a/include/Makefile.in
+++ b/include/Makefile.in
@@ -50,6 +50,7 @@ SOURCES = \
 	bits2_0.idl \
 	bits2_5.idl \
 	bits3_0.idl \
+	bits5_0.idl \
 	bitsmsg.h \
 	bluetoothapis.h \
 	bthsdpdef.h \
diff --git a/include/bits5_0.idl b/include/bits5_0.idl
new file mode 100644
index 00000000000..30b4e4bbe37
--- /dev/null
+++ b/include/bits5_0.idl
@@ -0,0 +1,36 @@
+#ifndef DO_NO_IMPORTS
+import "bits.idl";
+import "bits2_0.idl";
+import "bits3_0.idl";
+#endif
+
+cpp_quote("#define BITS_COST_STATE_TRANSFER_ALWAYS 0x800000ff")
+
+[
+    uuid(e847030c-bbba-4657-af6d-484aa42bf1fe),
+    odl
+]
+interface IBackgroundCopyJob5: IBackgroundCopyJob4
+{
+    typedef enum {
+        BITS_JOB_PROPERTY_ID_COST_FLAGS = 1,
+        BITS_JOB_PROPERTY_NOTIFICATION_CLSID = 2,
+        BITS_JOB_PROPERTY_DYNAMIC_CONTENT = 3,
+        BITS_JOB_PROPERTY_HIGH_PERFORMANCE = 4,
+        BITS_JOB_PROPERTY_MAX_DOWNLOAD_SIZE = 5,
+        BITS_JOB_PROPERTY_USE_STORED_CREDENTIALS = 7,
+        BITS_JOB_PROPERTY_MINIMUM_NOTIFICATION_INTERVAL_MS = 9,
+        BITS_JOB_PROPERTY_ON_DEMAND_MODE = 10,
+    } BITS_JOB_PROPERTY_ID;
+
+    typedef union _BITS_JOB_PROPERTY_VALUE {
+        DWORD Dword;
+        GUID ClsID;
+        BOOL Enable;
+        UINT64 Uint64;
+        BG_AUTH_TARGET Target;
+    } BITS_JOB_PROPERTY_VALUE;
+
+    HRESULT SetProperty(BITS_JOB_PROPERTY_ID id, BITS_JOB_PROPERTY_VALUE value);
+    HRESULT GetProperty(BITS_JOB_PROPERTY_ID id, [out, ref] BITS_JOB_PROPERTY_VALUE *value);
+}
-- 
2.29.1




More information about the wine-devel mailing list