get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/service_windows_test.go (about)

     1  // Copyright 2012-2019 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build windows
    15  
    16  package server
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"testing"
    23  	"time"
    24  
    25  	"golang.org/x/sys/windows/svc"
    26  )
    27  
    28  // TestWinServiceWrapper reproduces the tests for service,
    29  // with extra complication for windows API
    30  func TestWinServiceWrapper(t *testing.T) {
    31  	/*
    32  		Since the windows API can't be tested through just the Run func,
    33  		a basic mock checks the intractions of the server, as happens in svc.Run.
    34  		This test checks :
    35  		- that the service fails to start within an unreasonable timeframe (serverC)
    36  		- that the service signals its state correctly to windows with svc.StartPending (mockC)
    37  		- that no other signal is sent to the windows service API(mockC)
    38  	*/
    39  	var (
    40  		wsw     = &winServiceWrapper{New(DefaultOptions())}
    41  		args    = make([]string, 0)
    42  		serverC = make(chan error, 1)
    43  		mockC   = make(chan error, 1)
    44  		changes = make(chan svc.ChangeRequest)
    45  		status  = make(chan svc.Status)
    46  	)
    47  	time.Sleep(time.Millisecond)
    48  	os.Setenv("NATS_STARTUP_DELAY", "1ms") // purposefly small
    49  	// prepare mock expectations
    50  	wsm := &winSvcMock{status: status}
    51  	wsm.Expect(svc.StartPending)
    52  	go func() {
    53  		mockC <- wsm.Listen(50*time.Millisecond, t)
    54  		close(mockC)
    55  	}()
    56  
    57  	go func() { // ensure failure with these conditions
    58  		_, exitCode := wsw.Execute(args, changes, status)
    59  		if exitCode == 0 { // expect error
    60  			serverC <- errors.New("Should have exitCode != 0")
    61  		}
    62  		wsw.server.Shutdown()
    63  		close(serverC)
    64  	}()
    65  
    66  	for expectedErrs := 2; expectedErrs >= 0; {
    67  		select {
    68  		case err := <-mockC:
    69  			if err != nil {
    70  				t.Fatalf("windows.svc mock: %v", err)
    71  			} else {
    72  				expectedErrs--
    73  			}
    74  		case err := <-serverC:
    75  			if err != nil {
    76  				t.Fatalf("Server behavior: %v", err)
    77  			} else {
    78  				expectedErrs--
    79  			}
    80  		case <-time.After(2 * time.Second):
    81  			t.Fatal("Test timed out")
    82  		}
    83  	}
    84  }
    85  
    86  // winSvcMock mocks part of the golang.org/x/sys/windows/svc
    87  // execution stack, listening to svc.Status on its chan.
    88  type winSvcMock struct {
    89  	status     chan svc.Status
    90  	expectedSt []svc.State
    91  }
    92  
    93  // Expect allows to prepare a winSvcMock to receive a specific type of StatusMessage
    94  func (w *winSvcMock) Expect(st svc.State) {
    95  	w.expectedSt = append(w.expectedSt, st)
    96  }
    97  
    98  // Listen is the mock's mainloop, expects messages to comply with previous Expect().
    99  func (w *winSvcMock) Listen(dur time.Duration, t *testing.T) error {
   100  	t.Helper()
   101  	timeout := time.NewTimer(dur)
   102  	defer timeout.Stop()
   103  	for idx, state := range w.expectedSt {
   104  		select {
   105  		case status := <-w.status:
   106  			if status.State == state {
   107  				t.Logf("message %d on status, OK\n", idx)
   108  				continue
   109  			} else {
   110  				return fmt.Errorf("message to winsock: expected %v, got %v", state, status.State)
   111  			}
   112  		case <-timeout.C:
   113  			return errors.New("Mock timed out")
   114  		}
   115  	}
   116  	select {
   117  	case <-timeout.C:
   118  		return nil
   119  	case st := <-w.status:
   120  		return fmt.Errorf("extra message to winsock: got %v", st)
   121  
   122  	}
   123  }