io.github.microcks.service.TestService.java Source code

Java tutorial

Introduction

Here is the source code for io.github.microcks.service.TestService.java

Source

/*
 * 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;

/**
 * @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
     * @return An initialized TestResults (mostly empty for now since tests run asynchronously)
     */
    public TestResult launchTests(Service service, String testEndpoint, TestRunnerType runnerType) {
        TestResult testResult = new TestResult();
        testResult.setTestDate(new Date());
        testResult.setTestedEndpoint(testEndpoint);
        testResult.setServiceId(service.getId());
        testResult.setRunnerType(runnerType);
        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<TestReturn> testReturns) {
        log.info("Reporting a TestCaseResult for testResult {} on operation '{}'", testResultId, operationName);
        TestResult testResult = testResultRepository.findOne(testResultId);
        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()) {
                        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;
            } 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.findOne(testResult.getId());
                times++;
            }
        }
        return updatedTestCaseResult;
    }

    /**
     *
     */
    private void createTestReturns(List<TestReturn> testReturns, String testCaseId) {
        List<Response> responses = new ArrayList<Response>();
        List<Request> actualRequests = new ArrayList<Request>();

        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.save(responses);

        // Associate responses to requests before saving requests.
        for (int i = 0; i < actualRequests.size(); i++) {
            actualRequests.get(i).setResponseId(responses.get(i).getId());
        }
        log.debug("Saving {} requests with testCaseId {}", responses.size(), testCaseId);
        requestRepository.save(actualRequests);
    }

    /**
     *
     */
    private void updateTestCaseResultWithReturns(TestCaseResult testCaseResult, List<TestReturn> 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);
    }

    /**
     *
     */
    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;
            }
            if (testCaseResult.getElapsedTime() == 0) {
                globalProgressFlag = true;
            }
        }

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

        log.debug("Trying to update testResult {}", testResult.getId());
        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");
            }
        }
    }
}