All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.microcks.service.TestService Maven / Gradle / Ivy

There is a newer version: 1.11.2
Show newest version
/*
 * Licensed to Laurent Broudoux (the "Author") under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Author licenses this
 * file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package io.github.microcks.service;

import io.github.microcks.repository.RequestRepository;
import io.github.microcks.repository.ResponseRepository;
import io.github.microcks.repository.TestResultRepository;
import io.github.microcks.util.IdBuilder;
import io.github.microcks.util.test.TestReturn;
import io.github.microcks.domain.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Bean defining service operations around Test domain objects.
 * @author laurent
 */
@org.springframework.stereotype.Service
public class TestService {

   /** A simple logger for diagnostic messages. */
   private static Logger log = LoggerFactory.getLogger(TestService.class);

   @Autowired
   private RequestRepository requestRepository;

   @Autowired
   private ResponseRepository responseRepository;

   @Autowired
   private TestResultRepository testResultRepository;

   @Autowired
   private TestRunnerService testRunnerService;


   /**
    * Launch tests for a Service on dediated endpoint URI.
    * @param service Service to launch tests for
    * @param testEndpoint Endpoint URI for running the tests
    * @param runnerType The type of runner fo tests
    * @param operationsHeaders Additional / overriden headers for operations to test
    * @return An initialized TestResults (mostly empty for now since tests run asynchronously)
    */
   public TestResult launchTests(Service service, String testEndpoint, TestRunnerType runnerType, OperationsHeaders operationsHeaders){
      TestResult testResult = new TestResult();
      testResult.setTestDate(new Date());
      testResult.setTestedEndpoint(testEndpoint);
      testResult.setServiceId(service.getId());
      testResult.setRunnerType(runnerType);
      testResult.setOperationsHeaders(operationsHeaders);
      testResultRepository.save(testResult);

      // Launch test asynchronously before returning result.
      log.debug("Calling launchTestsInternal() marked as Async");
      testRunnerService.launchTestsInternal(testResult, service, runnerType);
      log.debug("Async launchTestsInternal() as now finished");
      return testResult;
   }

   /**
    * Endpoint for reporting test case results
    * @param testResultId Unique identifier of test results we report results for
    * @param operationName Name of operation to report a result for
    * @param testReturns List of test returns to add to this test case.
    * @return A completed TestCaseResult object
    */
   public TestCaseResult reportTestCaseResult(String testResultId, String operationName, List testReturns) {
      log.info("Reporting a TestCaseResult for testResult {} on operation '{}'", testResultId, operationName);
      TestResult testResult = testResultRepository.findById(testResultId).orElse(null);
      TestCaseResult updatedTestCaseResult = null;

      // This part can be done safely with no race condition because we only
      // record new requests/responses corresponding to testReturns.
      // So just find the correct testCase to build a suitable id and then createTestReturns.

      for (TestCaseResult testCaseResult : testResult.getTestCaseResults()) {
         // Ensure we have a testCaseResult matching operation name.
         if (testCaseResult.getOperationName().equals(operationName)) {
            // If results, we need to create requests/responses pairs and associate them to testCase.
            if (testReturns != null && !testReturns.isEmpty()) {
               String testCaseId = IdBuilder.buildTestCaseId(testResult, operationName);
               createTestReturns(testReturns, testCaseId);
            }
            break;
         }
      }

      // There may be a race condition while updating testResult at each testReturn report.
      // So be prepared to catch a org.springframework.dao.OptimisticLockingFailureException and retry
      // saving a bunch of time. Hopefully, we'll succeed. It does not matter if it takes time because
      // everything runs asynchronously.
      int times = 0;
      boolean saved = false;

      while (!saved && times < 5) {

         for (TestCaseResult testCaseResult : testResult.getTestCaseResults()) {
            // Ensure we have a testCaseResult matching operation name.
            if (testCaseResult.getOperationName().equals(operationName)) {
               updatedTestCaseResult = testCaseResult;
               // If results we now update the success flag and elapsed time of testCase?
               if (testReturns == null || testReturns.isEmpty()) {
                  log.info("testReturns are null or empty, setting elapsedTime to -I and success to false for {}", operationName);
                  testCaseResult.setElapsedTime(-1);
                  testCaseResult.setSuccess(false);
               } else {
                  updateTestCaseResultWithReturns(testCaseResult, testReturns);
               }
               break;
            }
         }

         // Finally, update success, progress indicators and total time before saving and returning.
         try {
            updateTestResult(testResult);
            saved = true;
            log.debug("testResult {} has been updated !", testResult.getId());
         } catch (org.springframework.dao.OptimisticLockingFailureException olfe) {
            // Update counter and refresh domain object.
            log.warn("Caught an OptimisticLockingFailureException, trying refreshing for {} times", times);
            saved = false;
            waitSomeRandomMS(5, 50);
            testResult = testResultRepository.findById(testResult.getId()).orElse(null);
            times++;
         }
      }
      return updatedTestCaseResult;
   }

   /**
    *
    */
   private void createTestReturns(List testReturns, String testCaseId) {
      List responses = new ArrayList();
      List actualRequests = new ArrayList();

      for (TestReturn testReturn : testReturns) {
         // Extract, complete and store response and request.
         testReturn.getResponse().setTestCaseId(testCaseId);
         testReturn.getRequest().setTestCaseId(testCaseId);
         responses.add(testReturn.getResponse());
         actualRequests.add(testReturn.getRequest());
      }

      // Save the responses into repository to get their ids.
      log.debug("Saving {} responses with testCaseId {}", responses.size(), testCaseId);
      responseRepository.saveAll(responses);

      // Associate responses to requests before saving requests.
      for (int i=0; i testReturns) {

      // Prepare a bunch of flag we're going to complete.
      boolean successFlag = true;
      long caseElapsedTime = 0;

      for (TestReturn testReturn : testReturns) {
         // Deal with elapsed time and success flag.
         caseElapsedTime += testReturn.getElapsedTime();
         TestStepResult testStepResult = testReturn.buildTestStepResult();
         if (!testStepResult.isSuccess()){
            successFlag = false;
         }

         // Add testStepResult to testCase.
         testCaseResult.getTestStepResults().add(testStepResult);
      }

      // Update and save the completed TestCaseResult.
      // We cannot consider as success if we have no TestStepResults associated...
      if (testCaseResult.getTestStepResults().size() > 0) {
         testCaseResult.setSuccess(successFlag);
      }
      testCaseResult.setElapsedTime(caseElapsedTime);
      log.debug("testCaseResult for {} have been updated with {} elapsedTime and success flag to {}",
            testCaseResult.getOperationName(), testCaseResult.getElapsedTime(), testCaseResult.isSuccess());
   }

   /**
    *
    */
   private void updateTestResult(TestResult testResult) {
      // Update success, progress indicators and total time before saving and returning.
      boolean globalSuccessFlag = true;
      boolean globalProgressFlag = false;
      long totalElapsedTime = 0;
      for (TestCaseResult testCaseResult : testResult.getTestCaseResults()) {
         totalElapsedTime += testCaseResult.getElapsedTime();
         if (!testCaseResult.isSuccess()){
            globalSuccessFlag = false;
         }
         // -1 is default elapsed time for testcase so its mean that still in
         // progress because not updated yet.
         if (testCaseResult.getElapsedTime() == -1) {
            log.debug("testCaseResult.elapsedTime is -1, set globalProgressFlag to true");
            globalProgressFlag = true;
         }
      }

      // Update aggregated flags before saving whole testResult.
      testResult.setSuccess(globalSuccessFlag);
      testResult.setInProgress(globalProgressFlag);
      testResult.setElapsedTime(totalElapsedTime);

      log.debug("Trying to update testResult {} with {} elapsedTime and success flag to {}",
            testResult.getId(), testResult.getElapsedTime(), testResult.isSuccess());
      testResultRepository.save(testResult);

   }

   private void waitSomeRandomMS(int min, int max) {
      Object semaphore = new Object();
      long timeout = ThreadLocalRandom.current().nextInt(min, max + 1);
      synchronized (semaphore) {
         try {
            semaphore.wait(timeout);
         } catch (Exception e) {
            log.debug("waitSomeRandomMS semaphore was interrupted");
         }
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy