RestartCounterTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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 org.apache.tika.pipes.core;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import org.apache.tika.pipes.core.protocol.PipesMessageType;
import org.apache.tika.pipes.core.server.PipesServer;

public class RestartCounterTest {

    @Test
    public void testMarkedReasonWins() {
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.MAX_FILES);
        c.restarted(PipesServer.IDLE_EXIT_CODE);
        assertEquals(1, c.count(RestartReason.MAX_FILES));
        assertEquals(0, c.count(RestartReason.IDLE));
    }

    @Test
    public void testLastMarkWins() {
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.CRASH);
        c.mark(RestartReason.OOM);
        c.restarted(-1);
        assertEquals(1, c.count(RestartReason.OOM));
        assertEquals(0, c.count(RestartReason.CRASH));
    }

    @Test
    public void testMarkIfUnmarkedKeepsSpecificReason() {
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.OOM);
        c.markIfUnmarked(RestartReason.CRASH);
        c.restarted(-1);
        assertEquals(1, c.count(RestartReason.OOM));
        c.markIfUnmarked(RestartReason.CRASH);
        c.restarted(-1);
        assertEquals(1, c.count(RestartReason.CRASH));
    }

    @ParameterizedTest
    @MethodSource("exitCodes")
    public void testUnmarkedAttributedByExitCode(int exitCode, RestartReason expected) {
        RestartCounter c = new RestartCounter();
        c.restarted(exitCode);
        for (RestartReason r : RestartReason.values()) {
            assertEquals(r == expected ? 1 : 0, c.count(r), r.name());
        }
    }

    /**
     * Enumerated from the codes the child can deliberately choose ({@link PipesMessageType} and
     * {@link PipesServer}), not from RestartCounter's own branches: a table derived from the
     * implementation cannot catch a case the implementation forgot.
     */
    static Stream<Arguments> exitCodes() {
        return Stream.of(
                Arguments.of(PipesServer.IDLE_EXIT_CODE, RestartReason.IDLE),
                Arguments.of(PipesMessageType.OOM.getExitCode().getAsInt(), RestartReason.OOM),
                Arguments.of(PipesMessageType.TIMEOUT.getExitCode().getAsInt(), RestartReason.TIMEOUT),
                Arguments.of(PipesMessageType.UNSPECIFIED_CRASH.getExitCode().getAsInt(), RestartReason.CRASH),
                Arguments.of(0, RestartReason.SHUTDOWN),
                Arguments.of(1, RestartReason.CRASH),
                Arguments.of(-1, RestartReason.CRASH));
    }

    @Test
    public void testFatalReasonOutranksScheduledRecycle() {
        // The file-limit boundary document may be the one that killed the worker; a scheduled
        // recycle must not overwrite the fatal reason recorded for the same pending restart.
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.OOM);
        c.markIfUnmarked(RestartReason.MAX_FILES);
        c.restarted(-1);
        assertEquals(1, c.count(RestartReason.OOM));
        assertEquals(0, c.count(RestartReason.MAX_FILES));
    }

    @Test
    public void testPendingClearsAfterRestart() {
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.TIMEOUT);
        c.restarted(-1);
        c.restarted(PipesServer.IDLE_EXIT_CODE);
        assertEquals(1, c.count(RestartReason.TIMEOUT));
        assertEquals(1, c.count(RestartReason.IDLE));
    }

    /** A mark left over from a start that never produced a process must not bleed into the next restart. */
    @Test
    public void testFirstStartNotCountedAndClearsMark() {
        RestartCounter c = new RestartCounter();
        c.mark(RestartReason.CRASH);
        c.restarted((Process) null);
        for (RestartReason r : RestartReason.values()) {
            assertEquals(0, c.count(r), r.name());
        }
        c.restarted(PipesServer.IDLE_EXIT_CODE);
        assertEquals(1, c.count(RestartReason.IDLE));
        assertEquals(0, c.count(RestartReason.CRASH));
    }
}