Skip to content

Commit

Permalink
Test the store wrapper error translation
Browse files Browse the repository at this point in the history
There is some non-negligible error translation in there
mostly due the somewhat involving error handling imposed by
syscall/dll_windows.
This adds a modest set of test cases to cover that logic.
For that to be approachable I extracted the error reporting
out of the proc call method, this way we don't depend on calling a DLL
function to test this part.
Quite artificial setup, but I always enjoy separating "computing" from
"doing".
  • Loading branch information
CarlosNihelton committed Aug 26, 2024
1 parent bb40136 commit e0efdbd
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 24 deletions.
3 changes: 3 additions & 0 deletions storeapi/go-wrapper/microsoftstore/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ package microsoftstore

// FindWorkspaceRoot climbs up the current working directory until the Go workspace root is found.
var FindWorkspaceRoot = findWorkspaceRoot

// CheckError inspects the values of hres and err to determine what kind of error we have, if any, according to the rules of syscall/dll_windows.go.
var CheckError = checkError
28 changes: 28 additions & 0 deletions storeapi/go-wrapper/microsoftstore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package microsoftstore

import (
"errors"
"fmt"
"os"
"path/filepath"
"syscall"
)

// findWorkspaceRoot climbs up the current working directory until the Go workspace root is found.
Expand All @@ -26,3 +28,29 @@ func findWorkspaceRoot() (string, error) {
}
}
}

// checkError inspects the values of hres and err to determine what kind of error we have, if any, according to the rules of syscall/dll_windows.go.
func checkError(hres int64, err error) (int64, error) {
// From syscall/dll_windows.go (*Proc).Call doc:
// > Callers must inspect the primary return value to decide whether an
// error occurred [...] before consulting the error.
if e := NewStoreAPIError(hres); e != nil {
return hres, fmt.Errorf("storeApi returned error code %d: %w", hres, e)
}

if err == nil {
return hres, nil
}

var target syscall.Errno
if b := errors.As(err, &target); !b {
// Supposedly unrechable: proc.Call must always return a syscall.Errno
return hres, err
}

if target != syscall.Errno(0) {
return hres, fmt.Errorf("failed syscall to storeApi: %v (syscall errno %d)", target, err)
}

return hres, nil

Check warning on line 55 in storeapi/go-wrapper/microsoftstore/store.go

View check run for this annotation

Codecov / codecov/patch

storeapi/go-wrapper/microsoftstore/store.go#L55

Added line #L55 was not covered by tests
}
29 changes: 29 additions & 0 deletions storeapi/go-wrapper/microsoftstore/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package microsoftstore_test

import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -87,6 +89,33 @@ func TestGetSubscriptionExpirationDate(t *testing.T) {
require.ErrorIs(t, gotErr, wantErr, "GetSubscriptionExpirationDate should have returned code %d", wantErr)
}

func TestErrorVerification(t *testing.T) {
t.Parallel()
testcases := map[string]struct {
hresult int64
err error

wantError bool
}{
"Success": {},
"Upper bound of the Store API enum range": {hresult: -1, wantError: true},
"Lower bound of the Store API enum range": {hresult: int64(microsoftstore.ErrNotSubscribed), wantError: true},
"With a system error (errno)": {hresult: 32 /*garbage*/, err: syscall.Errno(2) /*E_FILE_NOT_FOUND*/, wantError: true},
"With a generic (unreachable) error": {hresult: 1, err: errors.New("test error"), wantError: true},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
t.Parallel()
res, err := microsoftstore.CheckError(tc.hresult, tc.err)
if tc.wantError {
require.Error(t, err, "CheckError should have returned an error for value: %v, returned value was: %v", tc.hresult, res)
} else {
require.NoError(t, err, "CheckError should have not returned an error for value: %v, returned value was: %v", tc.hresult, res)
}
})
}
}

func buildStoreAPI(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
Expand Down
25 changes: 1 addition & 24 deletions storeapi/go-wrapper/microsoftstore/store_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package microsoftstore

import (
"errors"
"fmt"
"sync"
"syscall"
Expand Down Expand Up @@ -93,29 +92,7 @@ func call(proc *syscall.LazyProc, args ...uintptr) (int64, error) {

hresult, _, err := proc.Call(args...)
//nolint:gosec // Windows HRESULTS are guaranteed to be 32-bit vlaue, thus they surely fit inside a int64 without overflow.
hres := int64(hresult)
// From syscall/dll_windows.go (*Proc).Call doc:
// > Callers must inspect the primary return value to decide whether an
// error occurred [...] before consulting the error.
if err := NewStoreAPIError(hres); err != nil {
return hres, fmt.Errorf("storeApi returned error code %d: %w", hres, err)
}

if err == nil {
return hres, nil
}

var target syscall.Errno
if b := errors.As(err, &target); !b {
// Supposedly unrechable: proc.Call must always return a syscall.Errno
return hres, err
}

if target != syscall.Errno(0) {
return hres, fmt.Errorf("failed syscall to storeApi: %v (syscall errno %d)", target, err)
}

return hres, nil
return checkError(int64(hresult), err)
}

// loadDll finds the dll and ensures it loads.
Expand Down

0 comments on commit e0efdbd

Please sign in to comment.