/*
 * SonarQube
 * Copyright (C) 2009-2020 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.ce.taskprocessor;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.stubbing.Answer;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogAndArguments;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.ce.queue.InternalCeQueue;
import org.sonar.ce.task.CeTask;
import org.sonar.ce.task.CeTaskResult;
import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
import org.sonar.ce.task.taskprocessor.CeTaskProcessor;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.ce.CeActivityDto;
import org.sonar.db.ce.CeTaskTypes;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserTesting;
import org.sonar.server.organization.BillingValidations;

import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.ce.taskprocessor.CeWorker.Result.DISABLED;
import static org.sonar.ce.taskprocessor.CeWorker.Result.NO_TASK;
import static org.sonar.ce.taskprocessor.CeWorker.Result.TASK_PROCESSED;

public class CeWorkerImplTest {

  private System2 system2 = new TestSystem2().setNow(1_450_000_000_000L);

  @Rule
  public CeTaskProcessorRepositoryRule taskProcessorRepository = new CeTaskProcessorRepositoryRule();
  @Rule
  public LogTester logTester = new LogTester();
  @Rule
  public ExpectedException expectedException = ExpectedException.none();
  @Rule
  public DbTester db = DbTester.create(system2);

  private DbSession session = db.getSession();

  private InternalCeQueue queue = mock(InternalCeQueue.class);
  private ReportTaskProcessor taskProcessor = mock(ReportTaskProcessor.class);
  private CeWorker.ExecutionListener executionListener1 = mock(CeWorker.ExecutionListener.class);
  private CeWorker.ExecutionListener executionListener2 = mock(CeWorker.ExecutionListener.class);
  private CeWorkerController ceWorkerController = mock(CeWorkerController.class);
  private ArgumentCaptor<String> workerUuidCaptor = ArgumentCaptor.forClass(String.class);
  private int randomOrdinal = new Random().nextInt(50);
  private String workerUuid = UUID.randomUUID().toString();
  private CeWorker underTest = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController,
    executionListener1, executionListener2);
  private CeWorker underTestNoListener = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController);
  private InOrder inOrder = inOrder(taskProcessor, queue, executionListener1, executionListener2);
  private final CeTask.User submitter = new CeTask.User("UUID_USER_1", "LOGIN_1");

  @Before
  public void setUp() {
    when(ceWorkerController.isEnabled(any(CeWorker.class))).thenReturn(true);
  }

  @Test
  public void constructor_throws_IAE_if_ordinal_is_less_than_zero() {
    expectedException.expect(IllegalArgumentException.class);
    expectedException.expectMessage("Ordinal must be >= 0");

    new CeWorkerImpl(-1 - new Random().nextInt(20), workerUuid, queue, taskProcessorRepository, ceWorkerController);
  }

  @Test
  public void getUUID_must_return_the_uuid_of_constructor() {
    String uuid = UUID.randomUUID().toString();
    CeWorker underTest = new CeWorkerImpl(randomOrdinal, uuid, queue, taskProcessorRepository, ceWorkerController);
    assertThat(underTest.getUUID()).isEqualTo(uuid);
  }

  @Test
  public void worker_disabled() throws Exception {
    reset(ceWorkerController);
    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);

    assertThat(underTest.call()).isEqualTo(DISABLED);

    verifyZeroInteractions(taskProcessor, executionListener1, executionListener2);
  }

  @Test
  public void worker_disabled_no_listener() throws Exception {
    reset(ceWorkerController);
    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);

    assertThat(underTestNoListener.call()).isEqualTo(DISABLED);

    verifyZeroInteractions(taskProcessor, executionListener1, executionListener2);
  }

  @Test
  public void no_pending_tasks_in_queue() throws Exception {
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.empty());

    assertThat(underTest.call()).isEqualTo(NO_TASK);

    verifyZeroInteractions(taskProcessor, executionListener1, executionListener2);
  }

  @Test
  public void no_pending_tasks_in_queue_without_listener() throws Exception {
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.empty());

    assertThat(underTestNoListener.call()).isEqualTo(NO_TASK);

    verifyZeroInteractions(taskProcessor, executionListener1, executionListener2);
  }

  @Test
  public void fail_when_no_CeTaskProcessor_is_found_in_repository() throws Exception {
    CeTask task = createCeTask(null);
    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));

    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(executionListener1).onStart(task);
    inOrder.verify(executionListener2).onStart(task);
    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
  }

  @Test
  public void fail_when_no_CeTaskProcessor_is_found_in_repository_without_listener() throws Exception {
    CeTask task = createCeTask(null);
    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));

    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
    inOrder.verifyNoMoreInteractions();
  }

  @Test
  public void peek_and_process_task() throws Exception {
    CeTask task = createCeTask(null);
    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));

    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(executionListener1).onStart(task);
    inOrder.verify(executionListener2).onStart(task);
    inOrder.verify(taskProcessor).process(task);
    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
  }

  @Test
  public void peek_and_process_task_without_listeners() throws Exception {
    CeTask task = createCeTask(null);
    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));

    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(taskProcessor).process(task);
    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
    inOrder.verifyNoMoreInteractions();
  }

  @Test
  public void fail_to_process_task() throws Exception {
    CeTask task = createCeTask(null);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));
    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
    Throwable error = makeTaskProcessorFail(task);

    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(executionListener1).onStart(task);
    inOrder.verify(executionListener2).onStart(task);
    inOrder.verify(taskProcessor).process(task);
    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
  }

  @Test
  public void fail_to_process_task_without_listeners() throws Exception {
    CeTask task = createCeTask(null);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(task));
    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
    Throwable error = makeTaskProcessorFail(task);

    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);

    verifyWorkerUuid();
    inOrder.verify(taskProcessor).process(task);
    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
    inOrder.verifyNoMoreInteractions();
  }

  @Test
  public void log_task_characteristics() throws Exception {
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(null, "pullRequest", "123", "branch", "foo")));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    for (int i = 0; i < 2; i++) {
      assertThat(logs.get(i)).contains("pullRequest=123");
      assertThat(logs.get(i)).contains("branch=foo");
    }
  }

  @Test
  public void do_not_log_submitter_param_if_anonymous_and_success() throws Exception {
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(null)));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    for (int i = 0; i < 2; i++) {
      assertThat(logs.get(i)).doesNotContain("submitter=");
    }
  }

  @Test
  public void do_not_log_submitter_param_if_anonymous_and_error() throws Exception {
    CeTask ceTask = createCeTask(null);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
    makeTaskProcessorFail(ceTask);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).doesNotContain("submitter=");
    assertThat(logs.get(1)).doesNotContain("submitter=");
    logs = logTester.logs(LoggerLevel.ERROR);
    assertThat(logs).hasSize(1);
    assertThat(logs.iterator().next()).doesNotContain("submitter=");
    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
  }

  @Test
  public void log_submitter_login_if_authenticated_and_success() throws Exception {
    UserDto userDto = insertRandomUser();
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(toTaskSubmitter(userDto))));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=SUCCESS | time=", userDto.getLogin()));
    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
  }

  @Test
  public void log_submitterUuid_if_user_matching_submitterUuid_can_not_be_found() throws Exception {
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(new CeTask.User("UUID_USER", null))));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains("submitter=UUID_USER");
    assertThat(logs.get(1)).contains("submitter=UUID_USER | status=SUCCESS | time=");
    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
  }

  @Test
  public void display_submitterLogin_in_logs_when_set_in_case_of_error() throws Exception {
    UserDto userDto = insertRandomUser();
    CeTask ceTask = createCeTask(toTaskSubmitter(userDto));
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
    makeTaskProcessorFail(ceTask);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=FAILED | time=", userDto.getLogin()));
    logs = logTester.logs(LoggerLevel.ERROR);
    assertThat(logs).hasSize(1);
    assertThat(logs.get(0)).isEqualTo("Failed to execute task " + ceTask.getUuid());
  }

  @Test
  public void display_start_stop_at_debug_level_for_console_if_DEBUG_is_enabled_and_task_successful() throws Exception {
    logTester.setLevel(LoggerLevel.DEBUG);

    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=SUCCESS | time=", submitter.getLogin()));
    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
  }

  @Test
  public void display_start_at_debug_level_stop_at_error_level_for_console_if_DEBUG_is_enabled_and_task_failed() throws Exception {
    logTester.setLevel(LoggerLevel.DEBUG);

    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    makeTaskProcessorFail(ceTask);

    underTest.call();

    verifyWorkerUuid();
    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    logs = logTester.logs(LoggerLevel.ERROR);
    assertThat(logs).hasSize(1);
    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
  }

  @Test
  public void call_sets_and_restores_thread_name_with_information_of_worker_when_there_is_no_task_to_process() throws Exception {
    String threadName = randomAlphabetic(3);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenAnswer(invocation -> {
      assertThat(Thread.currentThread().getName())
        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
      return Optional.empty();
    });
    Thread newThread = createThreadNameVerifyingThread(threadName);

    newThread.start();
    newThread.join();
  }

  @Test
  public void call_sets_and_restores_thread_name_with_information_of_worker_when_a_task_is_processed() throws Exception {
    String threadName = randomAlphabetic(3);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenAnswer(invocation -> {
      assertThat(Thread.currentThread().getName())
        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
      return Optional.of(createCeTask(submitter));
    });
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    Thread newThread = createThreadNameVerifyingThread(threadName);

    newThread.start();
    newThread.join();
  }

  @Test
  public void call_sets_and_restores_thread_name_with_information_of_worker_when_an_error_occurs() throws Exception {
    String threadName = randomAlphabetic(3);
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenAnswer(invocation -> {
      assertThat(Thread.currentThread().getName())
        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
      return Optional.of(ceTask);
    });
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    makeTaskProcessorFail(ceTask);
    Thread newThread = createThreadNameVerifyingThread(threadName);

    newThread.start();
    newThread.join();
  }

  @Test
  public void call_sets_and_restores_thread_name_with_information_of_worker_when_worker_is_disabled() throws Exception {
    reset(ceWorkerController);
    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);

    String threadName = randomAlphabetic(3);
    Thread newThread = createThreadNameVerifyingThread(threadName);

    newThread.start();
    newThread.join();
  }

  @Test
  public void log_error_when_task_fails_with_not_MessageException() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    makeTaskProcessorFail(ceTask);

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    logs = logTester.logs(LoggerLevel.ERROR);
    assertThat(logs).hasSize(1);
    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
  }

  @Test
  public void do_no_log_error_when_task_fails_with_MessageException() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(1)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
  }

  @Test
  public void do_no_log_error_when_task_fails_with_BillingValidationsException() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    makeTaskProcessorFail(ceTask, new BillingValidations.BillingValidationsException("simulate MessageException thrown by TaskProcessor#process"));

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(1)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
  }

  @Test
  public void log_error_when_task_was_successful_but_ending_state_can_not_be_persisted_to_db() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    doThrow(new RuntimeException("Simulate queue#remove failing")).when(queue).remove(ceTask, CeActivityDto.Status.SUCCESS, null, null);

    underTest.call();

    assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
  }

  @Test
  public void log_error_when_task_failed_and_ending_state_can_not_be_persisted_to_db() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    IllegalStateException ex = makeTaskProcessorFail(ceTask);
    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
    assertThat(logAndArguments).hasSize(2);

    LogAndArguments executionErrorLog = logAndArguments.get(0);
    assertThat(executionErrorLog.getFormattedMsg()).isEqualTo("Failed to execute task " + ceTask.getUuid());
    assertThat(executionErrorLog.getArgs().get()).containsOnly(ceTask.getUuid(), ex);

    LogAndArguments finalizingErrorLog = logAndArguments.get(1);
    assertThat(finalizingErrorLog.getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
    Object arg1 = finalizingErrorLog.getArgs().get()[0];
    assertThat(arg1).isSameAs(runtimeException);
    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
  }

  @Test
  public void log_error_as_suppressed_when_task_failed_with_MessageException_and_ending_state_can_not_be_persisted_to_db() throws Exception {
    CeTask ceTask = createCeTask(submitter);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
    MessageException ex = makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));
    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);

    underTest.call();

    List<String> logs = logTester.logs(LoggerLevel.INFO);
    assertThat(logs).hasSize(2);
    assertThat(logs.get(0)).contains(" | submitter=" + submitter.getLogin());
    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.getLogin()));
    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
    assertThat(logAndArguments).hasSize(1);
    assertThat(logAndArguments.get(0).getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
    Object arg1 = logAndArguments.get(0).getArgs().get()[0];
    assertThat(arg1).isSameAs(runtimeException);
    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
  }

  @Test
  public void isExecutedBy_returns_false_when_no_interaction_with_instance() {
    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
  }

  @Test
  public void isExecutedBy_returns_false_unless_a_thread_is_currently_calling_call() throws InterruptedException {
    CountDownLatch inCallLatch = new CountDownLatch(1);
    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
    // mock long running peek(String) call => Thread is executing call() but not running a task
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
      inCallLatch.countDown();
      try {
        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
      return Optional.empty();
    });
    Thread t = callCallInNewThread(underTest);

    try {
      t.start();

      inCallLatch.await(10, TimeUnit.SECONDS);
      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
      assertThat(underTest.isExecutedBy(t)).isTrue();
    } finally {
      assertionsDoneLatch.countDown();
      t.join();
    }

    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
    assertThat(underTest.isExecutedBy(t)).isFalse();
  }

  @Test
  public void isExecutedBy_returns_false_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
    CountDownLatch inCallLatch = new CountDownLatch(1);
    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
    String taskType = randomAlphabetic(12);
    CeTask ceTask = mock(CeTask.class);
    when(ceTask.getType()).thenReturn(taskType);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {
      @CheckForNull
      @Override
      public CeTaskResult process(CeTask task) {
        inCallLatch.countDown();
        try {
          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
        return null;
      }
    });
    Thread t = callCallInNewThread(underTest);

    try {
      t.start();

      inCallLatch.await(10, TimeUnit.SECONDS);
      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
      assertThat(underTest.isExecutedBy(t)).isTrue();
    } finally {
      assertionsDoneLatch.countDown();
      t.join();
    }

    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
    assertThat(underTest.isExecutedBy(t)).isFalse();
  }

  @Test
  public void getCurrentTask_returns_empty_when_no_interaction_with_instance() {
    assertThat(underTest.getCurrentTask()).isEmpty();
  }

  @Test
  public void do_not_exclude_portfolio_when_indexation_task_lookup_is_disabled() throws Exception {
    // first call with empty queue to disable indexationTaskLookupEnabled
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.empty());
    assertThat(underTest.call()).isEqualTo(NO_TASK);

    ArgumentCaptor<Boolean> booleanCaptor = ArgumentCaptor.forClass(Boolean.class);
    // following calls should not exclude portfolios
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
    verify(queue, times(3)).peek(anyString(), anyBoolean(), booleanCaptor.capture());
    assertThat(booleanCaptor.getAllValues()).containsExactly(true, true, false);
  }

  @Test
  public void getCurrentTask_returns_empty_when_a_thread_is_currently_calling_call_but_not_executing_a_task() throws InterruptedException {
    CountDownLatch inCallLatch = new CountDownLatch(1);
    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
    // mock long running peek(String) call => Thread is executing call() but not running a task
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
      inCallLatch.countDown();
      try {
        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
      return Optional.empty();
    });
    Thread t = callCallInNewThread(underTest);

    try {
      t.start();

      inCallLatch.await(10, TimeUnit.SECONDS);
      assertThat(underTest.getCurrentTask()).isEmpty();
    } finally {
      assertionsDoneLatch.countDown();
      t.join();
    }

    assertThat(underTest.getCurrentTask()).isEmpty();
  }

  @Test
  public void getCurrentTask_returns_empty_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
    CountDownLatch inCallLatch = new CountDownLatch(1);
    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
    String taskType = randomAlphabetic(12);
    CeTask ceTask = mock(CeTask.class);
    when(ceTask.getType()).thenReturn(taskType);
    when(queue.peek(anyString(), anyBoolean(), anyBoolean())).thenReturn(Optional.of(ceTask));
    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {

      @CheckForNull
      @Override
      public CeTaskResult process(CeTask task) {
        inCallLatch.countDown();
        try {
          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
        return null;
      }
    });
    Thread t = callCallInNewThread(underTest);

    try {
      t.start();

      inCallLatch.await(10, TimeUnit.SECONDS);
      assertThat(underTest.getCurrentTask()).contains(ceTask);
    } finally {
      assertionsDoneLatch.countDown();
      t.join();
    }

    assertThat(underTest.getCurrentTask()).isEmpty();
  }

  private Thread callCallInNewThread(CeWorker underTest) {
    return new Thread(() -> {
      try {
        underTest.call();
      } catch (Exception e) {
        throw new RuntimeException("call to call() failed and this is unexpected. Fix the UT.", e);
      }
    });
  }

  private Thread createThreadNameVerifyingThread(String threadName) {
    return new Thread(() -> {
      verifyUnchangedThreadName(threadName);
      try {
        underTest.call();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
      verifyUnchangedThreadName(threadName);
    }, threadName);
  }

  private void verifyUnchangedThreadName(String threadName) {
    assertThat(Thread.currentThread().getName()).isEqualTo(threadName);
  }

  private void verifyWorkerUuid() {
    verify(queue, atLeastOnce()).peek(workerUuidCaptor.capture(), anyBoolean(), anyBoolean());
    assertThat(workerUuidCaptor.getValue()).isEqualTo(workerUuid);
  }

  private static CeTask createCeTask(@Nullable CeTask.User submitter, String... characteristics) {
    Map<String, String> characteristicMap = new HashMap<>();
    for (int i = 0; i < characteristics.length; i += 2) {
      characteristicMap.put(characteristics[i], characteristics[i + 1]);
    }
    CeTask.Component component = new CeTask.Component("PROJECT_1", null, null);
    return new CeTask.Builder()
      .setOrganizationUuid("org1")
      .setUuid("TASK_1").setType(CeTaskTypes.REPORT)
      .setComponent(component)
      .setMainComponent(component)
      .setSubmitter(submitter)
      .setCharacteristics(characteristicMap)
      .build();
  }

  private UserDto insertRandomUser() {
    UserDto userDto = UserTesting.newUserDto();
    db.getDbClient().userDao().insert(session, userDto);
    session.commit();
    return userDto;
  }

  private CeTask.User toTaskSubmitter(UserDto userDto) {
    return new CeTask.User(userDto.getUuid(), userDto.getLogin());
  }

  private IllegalStateException makeTaskProcessorFail(CeTask task) {
    return makeTaskProcessorFail(task, new IllegalStateException("simulate exception thrown by TaskProcessor#process"));
  }

  private <T extends Throwable> T makeTaskProcessorFail(CeTask task, T t) {
    doThrow(t).when(taskProcessor).process(task);
    return t;
  }

  private static abstract class SimpleCeTaskProcessor implements CeTaskProcessor {
    @Override
    public Set<String> getHandledCeTaskTypes() {
      throw new UnsupportedOperationException("getHandledCeTaskTypes should not be called");
    }
  }
}

{'repo_name': 'SonarSource/sonarqube', 'stars': '4904', 'repo_language': 'Java', 'file_name': 'WebServiceTest.java', 'mime_type': 'text/x-java', 'hash': 2571538603827180707, 'source_dataset': 'data'}


<?php

/**
 *  @version
 *  @file
 *  @author CapriSkye <admin@capriskye.com> http://open.38.com
 *  EN-Revision: Revision of lang_en.inc.php
 */

@define('PLUGIN_EVENT_SPAMBLOCK_TITLE', '阻擋保護');
@define('PLUGIN_EVENT_SPAMBLOCK_DESC', '阻擋垃圾迴響的保護方法');
@define('PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY', '阻擋保護：錯誤訊息。');
@define('PLUGIN_EVENT_SPAMBLOCK_ERROR_IP', '阻擋保護：您不能連續發佈迴響。');

@define('PLUGIN_EVENT_SPAMBLOCK_ERROR_KILLSWITCH', '此網誌開啟了 "緊急迴響保護模式"，請稍後在試');
@define('PLUGIN_EVENT_SPAMBLOCK_BODYCLONE', '不允許相同迴響');
@define('PLUGIN_EVENT_SPAMBLOCK_BODYCLONE_DESC', '不允許訪客在同一篇文章內發佈相同的迴響');
@define('PLUGIN_EVENT_SPAMBLOCK_KILLSWITCH', '緊急迴響保護模式');
@define('PLUGIN_EVENT_SPAMBLOCK_KILLSWITCH_DESC', '暫時關閉全部文章的迴響功能。可在遭受垃圾廣告攻擊時開啟。');
@define('PLUGIN_EVENT_SPAMBLOCK_IPFLOOD', 'IP 阻擋時間');
@define('PLUGIN_EVENT_SPAMBLOCK_IPFLOOD_DESC', '主允許同一個 IP 在 X 時間內發佈迴響。可避免遭受迴響灌水。');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS', '開啟 Captchas');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_DESC', '訪客要留言時必須輸入圖片裡出現的文字。可允許自動廣告程式輸入迴響。記得有些訪客可能無法解讀圖片裡的文字。');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_USERDESC', '為了避免自動廣告程式輸入迴響，請輸入圖片裡的文字。如果文字正確，迴響就可以正常發佈。記得瀏覽器必須支援 cookies 要不然您的迴響無法通過測驗。');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_USERDESC2', '請輸入欄位裡出現的文字。');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_USERDESC3', '請輸入圖片裡出現的文字： ');
@define('PLUGIN_EVENT_SPAMBLOCK_ERROR_CAPTCHAS', '您輸入的文字錯誤，請檢查是否輸入正確。');
@define('PLUGIN_EVENT_SPAMBLOCK_ERROR_NOTTF', '您的主機不支援 Captchas。您的 PHP 需要 GDLib 和 freetype 存庫，和 .TTF 檔案必須在您的目錄內。');

@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_TTL', '數天後強制開啟 captchas');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_TTL_DESC', 'Captchas 可以在數天之後自動開啟。請輸入要開啟 captchas 的天數。如果輸入 0，captchas 會永遠開啟。');
@define('PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATION', '數天後強制開啟迴響管理');
@define('PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATION_DESC', '您可以設定是否管理迴響的發佈。輸入文章發佈的時間，之後全部的迴響都需要管理員審核。輸入 0 表示每個迴響都不需要管理審核。');
@define('PLUGIN_EVENT_SPAMBLOCK_LINKS_MODERATE', '多少連結需要管理審核');
@define('PLUGIN_EVENT_SPAMBLOCK_LINKS_MODERATE_DESC', '當迴響裡的連結超過限制就必須要管理員的審核才會發佈。輸入 0 如果不檢查連結數量。');
@define('PLUGIN_EVENT_SPAMBLOCK_LINKS_REJECT', '多少連結會自動拒絕');
@define('PLUGIN_EVENT_SPAMBLOCK_LINKS_REJECT_DESC', '當迴響裡的連結超過限制就不會通過自動管理。輸入 0 如果不檢查連結數量。');

@define('PLUGIN_EVENT_SPAMBLOCK_NOTICE_MODERATION', '因為迴響的限制，您的迴響需通過作者的審核。');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHA_COLOR', 'captcha 的背景顏色');
@define('PLUGIN_EVENT_SPAMBLOCK_CAPTCHA_COLOR_DESC', '輸入 RGB 顏色值：0,255,255');

@define('PLUGIN_EVENT_SPAMBLOCK_LOGFILE', '記錄檔案位址');
@define('PLUGIN_EVENT_SPAMBLOCK_LOGFILE_DESC', '拒絕/管理 的文章會記錄到檔案內。如果不想記錄可以輸入空白。');

@define('PLUGIN_EVENT_SPAMBLOCK_REASON_KILLSWITCH', '緊急迴響保護');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_BODYCLONE', '相同迴響');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_IPFLOOD', 'IP 阻擋');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_CAPTCHAS', '錯誤 captcha (輸入：%s，正確：%s)');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_FORCEMODERATION', '數天後自動管理');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_LINKS_REJECT', '連結過多');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_LINKS_MODERATE', '連結過多');
@define('PLUGIN_EVENT_SPAMBLOCK_HIDE_EMAIL', '隱藏留言者的電子信箱');
@define('PLUGIN_EVENT_SPAMBLOCK_HIDE_EMAIL_DESC', '不顯示發佈迴響的訪客的電子信箱');
@define('PLUGIN_EVENT_SPAMBLOCK_HIDE_EMAIL_NOTICE', '電子信箱將不會顯示，僅用於發佈通知');

@define('PLUGIN_EVENT_SPAMBLOCK_LOGTYPE', '記錄方法');
@define('PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_DESC', '記錄的資料可以存到資料庫或文字檔');
@define('PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_FILE', '文字檔 (看下面的記錄檔選項)');
@define('PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_DB', '資料庫');
@define('PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_NONE', '不記錄');

@define('PLUGIN_EVENT_SPAMBLOCK_API_COMMENTS', '如何處理 APIs 的迴響');
@define('PLUGIN_EVENT_SPAMBLOCK_API_COMMENTS_DESC', '這會管理從 API calls 來的迴響 (引用，WFW:commentAPI 的迴響)。如果選擇 "管理"，全部的迴響都需要通過審核。如果選擇 "拒絕"，全部的迴響都不允許。如果選擇 "沒有"，全部的迴響將會以普通迴響來處理。');
@define('PLUGIN_EVENT_SPAMBLOCK_API_MODERATE', '管理');
@define('PLUGIN_EVENT_SPAMBLOCK_API_REJECT', '拒絕');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_API', '不允許 API 建立的迴響 (像引用)');

@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_ACTIVATE', '開啟特殊字');
@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_ACTIVATE_DESC', '搜尋迴響裡的訊息，如果出現特殊字將標示為垃圾廣告。');

@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS', '特殊 URLs');
@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS_DESC', '可用正規運算式，以分號 (;) 來分開字串。');
@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS', '特殊發佈名稱');
@define('PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS_DESC', '可用正規運算式，以分號 (;) 來分開字串。');

@define('PLUGIN_EVENT_SPAMBLOCK_REASON_CHECKMAIL', '錯誤的電子郵件');
@define('PLUGIN_EVENT_SPAMBLOCK_CHECKMAIL', '顯查電子郵件？');
@define('PLUGIN_EVENT_SPAMBLOCK_REQUIRED_FIELDS', '需要迴響欄位');
@define('PLUGIN_EVENT_SPAMBLOCK_REQUIRED_FIELDS_DESC', '輸入訪客留言時必填的欄位。請用逗點 "," 來分開每個欄位。可用的自有： name, email, url, replyTo, comment');
@define('PLUGIN_EVENT_SPAMBLOCK_REASON_REQUIRED_FIELD', '您沒有輸入 %s 欄位！');

@define('PLUGIN_EVENT_SPAMBLOCK_CONFIG', '設定垃圾阻擋的方法');
@define('PLUGIN_EVENT_SPAMBLOCK_ADD_AUTHOR', '用 Spamblock 外掛阻擋這位訪客');
@define('PLUGIN_EVENT_SPAMBLOCK_ADD_URL', '用 Spamblock 外掛阻擋這個網址 URL');
@define('PLUGIN_EVENT_SPAMBLOCK_REMOVE_AUTHOR', '用 Spamblock 外掛允許這位訪客');
@define('PLUGIN_EVENT_SPAMBLOCK_REMOVE_URL', '用 Spamblock 外掛允許這個網址 URL');

{'repo_name': 's9y/Serendipity', 'stars': '139', 'repo_language': 'PHP', 'file_name': 'ChangeLog', 'mime_type': 'text/plain', 'hash': 6136955417620301912, 'source_dataset': 'data'}


---
layout: docs
page_title: 'Commands: job eval'
sidebar_title: eval
description: |
  The job eval command is used to force an evaluation of a job
---

# Command: job eval

The `job eval` command is used to force an evaluation of a job, given the job
ID.

## Usage

```plaintext
nomad job eval [options] <job_id>
```

The `job eval` command requires a single argument, specifying the job ID to
evaluate. If there is an exact match based on the provided job ID, then the job
will be evaluated, forcing a scheduler run.

## General Options

@include 'general_options.mdx'

## Eval Options

- `-force-reschedule`: `force-reschedule` is used to force placement of failed
  allocations. If this is set, failed allocations that are past their reschedule
  limit, and those that are scheduled to be replaced at a future time are placed
  immediately. This option only places failed allocations if the task group has
  rescheduling enabled.

- `-detach`: Return immediately instead of monitoring. A new evaluation ID
  will be output, which can be used to examine the evaluation using the
  [eval status] command.

- `-verbose`: Show full information.

## Examples

Evaluate the job with ID "job1":

```shell-session
$ nomad job eval job1
==> Monitoring evaluation "0f3bc0f3"
    Evaluation triggered by job "test"
    Evaluation within deployment: "51baf5c8"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "0f3bc0f3" finished with status "complete"
```

Evaluate the job with ID "job1" and return immediately:

```shell-session
$ nomad job eval -detach job1
Created eval ID: "4947e728"
```

Evaluate the job with ID "job1", and reschedule any eligible failed allocations:

```shell-session
$ nomad job eval -force-reschedule job1
==> Monitoring evaluation "0f3bc0f3"
    Evaluation triggered by job "test"
    Evaluation within deployment: "51baf5c8"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "0f3bc0f3" finished with status "complete"
```

[eval status]: /docs/commands/eval-status

{'repo_name': 'hashicorp/nomad', 'stars': '6520', 'repo_language': 'Go', 'file_name': 'client.go', 'mime_type': 'text/plain', 'hash': -2975378554866000119, 'source_dataset': 'data'}


using System;
using System.Reflection;
using System.Text;

namespace NsqSharp.Bus.Configuration.BuiltIn
{
    /// <summary>
    /// Convenience class for creating a Newtonsoft.Json message serializer. See <see cref="BusConfiguration"/>.
    /// </summary>
    public class NewtonsoftJsonSerializer : IMessageSerializer
    {
        private readonly Func<object, byte[]> _serializer;
        private readonly Func<Type, byte[], object> _deserializer;

        /// <summary>
        /// Initializes a new instance of the <see cref="NewtonsoftJsonSerializer"/> class. See <see cref="BusConfiguration"/>.
        /// </summary>
        /// <param name="newtonsoftJsonAssembly">The Newtonsoft.Json assembly; typically typeof(JsonConvert).Assembly.</param>
        public NewtonsoftJsonSerializer(Assembly newtonsoftJsonAssembly)
        {
            if (newtonsoftJsonAssembly == null)
                throw new ArgumentNullException("newtonsoftJsonAssembly");

            var jsonConvertType = newtonsoftJsonAssembly.GetType("Newtonsoft.Json.JsonConvert", throwOnError: true);
            var jsonConvertMethods = jsonConvertType.GetMethods(BindingFlags.Public | BindingFlags.Static);

            Func<object, byte[]> serializer = null;
            Func<Type, byte[], object> deserializer = null;

            foreach (var method in jsonConvertMethods)
            {
                bool isSerializeObject = (method.Name == "SerializeObject");
                bool isDeserializeObject = (method.Name == "DeserializeObject");

                if (isSerializeObject || isDeserializeObject)
                {
                    var genericArgs = method.GetGenericArguments();
                    var parameters = method.GetParameters();

                    if (genericArgs.Length == 0)
                    {
                        if (isSerializeObject && parameters.Length == 1)
                        {
                            if (parameters[0].ParameterType == typeof(object))
                            {
                                var serializeMethod = method;
                                serializer = obj =>
                                    Encoding.UTF8.GetBytes((string)serializeMethod.Invoke(null, new[] { obj }));
                            }
                        }
                        else if (isDeserializeObject && parameters.Length == 2)
                        {
                            if (parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(Type))
                            {
                                var deserializeMethod = method;
                                deserializer = (type, byteArray) =>
                                    deserializeMethod.Invoke(null, new object[] { Encoding.UTF8.GetString(byteArray), type });
                            }
                        }
                    }
                }
            }

            if (serializer == null)
                throw new Exception("Cannot find Newtonsoft.Json.JsonConvert.SerializeObject static method");
            if (deserializer == null)
                throw new Exception("Cannot find Newtonsoft.Json.JsonConvert.DeserializeObject static method");

            _serializer = serializer;
            _deserializer = deserializer;
        }

        /// <summary>
        /// Serializes the specified <paramref name="value"/> to a byte array.
        /// </summary>
        /// <param name="value">The value to serialize.</param>
        /// <returns>The serialized value.</returns>
        public byte[] Serialize(object value)
        {
            return _serializer(value);
        }

        /// <summary>
        /// Deserializes the specified <paramref name="value"/> to an object of type <paramref name="type" />.
        /// </summary>
        /// <param name="type">The type of the deserialized object.</param>
        /// <param name="value">The value to deserialize.</param>
        /// <returns>The deserialized object.</returns>
        public object Deserialize(Type type, byte[] value)
        {
            return _deserializer(type, value);
        }
    }
}

{'repo_name': 'judwhite/NsqSharp', 'stars': '133', 'repo_language': 'C#', 'file_name': 'AssemblyInfo.cs', 'mime_type': 'text/plain', 'hash': 2661644896466622766, 'source_dataset': 'data'}


/*
 * Copyright (C) STMicroelectronics SA 2015
 * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
 *          for STMicroelectronics.
 * License terms:  GNU General Public License (GPL), version 2
 */

#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/delay.h>

#include "uniperif.h"

/*
 * User frame size shall be 2, 4, 6 or 8 32-bits words length
 * (i.e. 8, 16, 24 or 32 bytes)
 * This constraint comes from allowed values for
 * UNIPERIF_I2S_FMT_NUM_CH register
 */
#define UNIPERIF_MAX_FRAME_SZ 0x20
#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ)

struct sti_uniperiph_dev_data {
	unsigned int id; /* Nb available player instances */
	unsigned int version; /* player IP version */
	unsigned int stream;
	const char *dai_names;
	enum uniperif_type type;
};

static const struct sti_uniperiph_dev_data sti_uniplayer_hdmi = {
	.id = 0,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #0 (HDMI)",
	.type = SND_ST_UNIPERIF_TYPE_HDMI
};

static const struct sti_uniperiph_dev_data sti_uniplayer_pcm_out = {
	.id = 1,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #1 (PCM OUT)",
	.type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM,
};

static const struct sti_uniperiph_dev_data sti_uniplayer_dac = {
	.id = 2,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #2 (DAC)",
	.type = SND_ST_UNIPERIF_TYPE_PCM,
};

static const struct sti_uniperiph_dev_data sti_uniplayer_spdif = {
	.id = 3,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #3 (SPDIF)",
	.type = SND_ST_UNIPERIF_TYPE_SPDIF
};

static const struct sti_uniperiph_dev_data sti_unireader_pcm_in = {
	.id = 0,
	.version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0,
	.stream = SNDRV_PCM_STREAM_CAPTURE,
	.dai_names = "Uni Reader #0 (PCM IN)",
	.type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM,
};

static const struct sti_uniperiph_dev_data sti_unireader_hdmi_in = {
	.id = 1,
	.version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0,
	.stream = SNDRV_PCM_STREAM_CAPTURE,
	.dai_names = "Uni Reader #1 (HDMI IN)",
	.type = SND_ST_UNIPERIF_TYPE_PCM,
};

static const struct of_device_id snd_soc_sti_match[] = {
	{ .compatible = "st,stih407-uni-player-hdmi",
	  .data = &sti_uniplayer_hdmi
	},
	{ .compatible = "st,stih407-uni-player-pcm-out",
	  .data = &sti_uniplayer_pcm_out
	},
	{ .compatible = "st,stih407-uni-player-dac",
	  .data = &sti_uniplayer_dac
	},
	{ .compatible = "st,stih407-uni-player-spdif",
	  .data = &sti_uniplayer_spdif
	},
	{ .compatible = "st,stih407-uni-reader-pcm_in",
	  .data = &sti_unireader_pcm_in
	},
	{ .compatible = "st,stih407-uni-reader-hdmi",
	  .data = &sti_unireader_hdmi_in
	},
	{},
};

int  sti_uniperiph_reset(struct uniperif *uni)
{
	int count = 10;

	/* Reset uniperipheral uni */
	SET_UNIPERIF_SOFT_RST_SOFT_RST(uni);

	if (uni->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) {
		while (GET_UNIPERIF_SOFT_RST_SOFT_RST(uni) && count) {
			udelay(5);
			count--;
		}
	}

	if (!count) {
		dev_err(uni->dev, "Failed to reset uniperif\n");
		return -EIO;
	}

	return 0;
}

int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
			       unsigned int rx_mask, int slots,
			       int slot_width)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int i, frame_size, avail_slots;

	if (!UNIPERIF_TYPE_IS_TDM(uni)) {
		dev_err(uni->dev, "cpu dai not in tdm mode\n");
		return -EINVAL;
	}

	/* store info in unip context */
	uni->tdm_slot.slots = slots;
	uni->tdm_slot.slot_width = slot_width;
	/* unip is unidirectionnal */
	uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask;

	/* number of available timeslots */
	for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) {
		if ((uni->tdm_slot.mask >> i) & 0x01)
			avail_slots++;
	}
	uni->tdm_slot.avail_slots = avail_slots;

	/* frame size in bytes */
	frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8;

	/* check frame size is allowed */
	if ((frame_size > UNIPERIF_MAX_FRAME_SZ) ||
	    (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) {
		dev_err(uni->dev, "frame size not allowed: %d bytes\n",
			frame_size);
		return -EINVAL;
	}

	return 0;
}

int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params,
			       struct snd_pcm_hw_rule *rule)
{
	struct uniperif *uni = rule->private;
	struct snd_interval t;

	t.min = uni->tdm_slot.avail_slots;
	t.max = uni->tdm_slot.avail_slots;
	t.openmin = 0;
	t.openmax = 0;
	t.integer = 0;

	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
}

int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params,
				 struct snd_pcm_hw_rule *rule)
{
	struct uniperif *uni = rule->private;
	struct snd_mask *maskp = hw_param_mask(params, rule->var);
	u64 format;

	switch (uni->tdm_slot.slot_width) {
	case 16:
		format = SNDRV_PCM_FMTBIT_S16_LE;
		break;
	case 32:
		format = SNDRV_PCM_FMTBIT_S32_LE;
		break;
	default:
		dev_err(uni->dev, "format not supported: %d bits\n",
			uni->tdm_slot.slot_width);
		return -EINVAL;
	}

	maskp->bits[0] &= (u_int32_t)format;
	maskp->bits[1] &= (u_int32_t)(format >> 32);
	/* clear remaining indexes */
	memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8);

	if (!maskp->bits[0] && !maskp->bits[1])
		return -EINVAL;

	return 0;
}

int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni,
				   unsigned int *word_pos)
{
	int slot_width = uni->tdm_slot.slot_width / 8;
	int slots_num = uni->tdm_slot.slots;
	unsigned int slots_mask = uni->tdm_slot.mask;
	int i, j, k;
	unsigned int word16_pos[4];

	/* word16_pos:
	 * word16_pos[0] = WORDX_LSB
	 * word16_pos[1] = WORDX_MSB,
	 * word16_pos[2] = WORDX+1_LSB
	 * word16_pos[3] = WORDX+1_MSB
	 */

	/* set unip word position */
	for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) {
		if ((slots_mask >> i) & 0x01) {
			word16_pos[j] = i * slot_width;

			if (slot_width == 4) {
				word16_pos[j + 1] = word16_pos[j] + 2;
				j++;
			}
			j++;

			if (j > 3) {
				word_pos[k] = word16_pos[1] |
					      (word16_pos[0] << 8) |
					      (word16_pos[3] << 16) |
					      (word16_pos[2] << 24);
				j = 0;
				k++;
			}
		}
	}

	return 0;
}

/*
 * sti_uniperiph_dai_create_ctrl
 * This function is used to create Ctrl associated to DAI but also pcm device.
 * Request is done by front end to associate ctrl with pcm device id
 */
static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	struct snd_kcontrol_new *ctrl;
	int i;

	if (!uni->num_ctrls)
		return 0;

	for (i = 0; i < uni->num_ctrls; i++) {
		/*
		 * Several Control can have same name. Controls are indexed on
		 * Uniperipheral instance ID
		 */
		ctrl = &uni->snd_ctrls[i];
		ctrl->index = uni->id;
		ctrl->device = uni->id;
	}

	return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls);
}

/*
 * DAI
 */
int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *params,
				struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	struct snd_dmaengine_dai_dma_data *dma_data;
	int transfer_size;

	if (uni->type == SND_ST_UNIPERIF_TYPE_TDM)
		/* transfer size = user frame size (in 32-bits FIFO cell) */
		transfer_size = snd_soc_params_to_frame_size(params) / 32;
	else
		transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;

	dma_data = snd_soc_dai_get_dma_data(dai, substream);
	dma_data->maxburst = transfer_size;

	return 0;
}

int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);

	priv->dai_data.uni->daifmt = fmt;

	return 0;
}

static int sti_uniperiph_dai_suspend(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int ret;

	/* The uniperipheral should be in stopped state */
	if (uni->state != UNIPERIF_STATE_STOPPED) {
		dev_err(uni->dev, "%s: invalid uni state( %d)\n",
			__func__, (int)uni->state);
		return -EBUSY;
	}

	/* Pinctrl: switch pinstate to sleep */
	ret = pinctrl_pm_select_sleep_state(uni->dev);
	if (ret)
		dev_err(uni->dev, "%s: failed to select pinctrl state\n",
			__func__);

	return ret;
}

static int sti_uniperiph_dai_resume(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int ret;

	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = uni_player_resume(uni);
		if (ret)
			return ret;
	}

	/* pinctrl: switch pinstate to default */
	ret = pinctrl_pm_select_default_state(uni->dev);
	if (ret)
		dev_err(uni->dev, "%s: failed to select pinctrl state\n",
			__func__);

	return ret;
}

static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct sti_uniperiph_dai *dai_data = &priv->dai_data;

	/* DMA settings*/
	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK)
		snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL);
	else
		snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data);

	dai_data->dma_data.addr = dai_data->uni->fifo_phys_address;
	dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

	return sti_uniperiph_dai_create_ctrl(dai);
}

static const struct snd_soc_dai_driver sti_uniperiph_dai_template = {
	.probe = sti_uniperiph_dai_probe,
	.suspend = sti_uniperiph_dai_suspend,
	.resume = sti_uniperiph_dai_resume
};

static const struct snd_soc_component_driver sti_uniperiph_dai_component = {
	.name = "sti_cpu_dai",
};

static int sti_uniperiph_cpu_dai_of(struct device_node *node,
				    struct sti_uniperiph_data *priv)
{
	struct device *dev = &priv->pdev->dev;
	struct sti_uniperiph_dai *dai_data = &priv->dai_data;
	struct snd_soc_dai_driver *dai = priv->dai;
	struct snd_soc_pcm_stream *stream;
	struct uniperif *uni;
	const struct of_device_id *of_id;
	const struct sti_uniperiph_dev_data *dev_data;
	const char *mode;
	int ret;

	/* Populate data structure depending on compatibility */
	of_id = of_match_node(snd_soc_sti_match, node);
	if (!of_id->data) {
		dev_err(dev, "data associated to device is missing\n");
		return -EINVAL;
	}
	dev_data = (struct sti_uniperiph_dev_data *)of_id->data;

	uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL);
	if (!uni)
		return -ENOMEM;

	uni->id = dev_data->id;
	uni->ver = dev_data->version;

	*dai = sti_uniperiph_dai_template;
	dai->name = dev_data->dai_names;

	/* Get resources */
	uni->mem_region = platform_get_resource(priv->pdev, IORESOURCE_MEM, 0);

	if (!uni->mem_region) {
		dev_err(dev, "Failed to get memory resource\n");
		return -ENODEV;
	}

	uni->base = devm_ioremap_resource(dev, uni->mem_region);

	if (IS_ERR(uni->base))
		return PTR_ERR(uni->base);

	uni->fifo_phys_address = uni->mem_region->start +
				     UNIPERIF_FIFO_DATA_OFFSET(uni);

	uni->irq = platform_get_irq(priv->pdev, 0);
	if (uni->irq < 0) {
		dev_err(dev, "Failed to get IRQ resource\n");
		return -ENXIO;
	}

	uni->type = dev_data->type;

	/* check if player should be configured for tdm */
	if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) {
		if (!of_property_read_string(node, "st,tdm-mode", &mode))
			uni->type = SND_ST_UNIPERIF_TYPE_TDM;
		else
			uni->type = SND_ST_UNIPERIF_TYPE_PCM;
	}

	dai_data->uni = uni;
	dai_data->stream = dev_data->stream;

	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = uni_player_init(priv->pdev, uni);
		stream = &dai->playback;
	} else {
		ret = uni_reader_init(priv->pdev, uni);
		stream = &dai->capture;
	}
	if (ret < 0)
		return ret;

	dai->ops = uni->dai_ops;

	stream->stream_name = dai->name;
	stream->channels_min = uni->hw->channels_min;
	stream->channels_max = uni->hw->channels_max;
	stream->rates = uni->hw->rates;
	stream->formats = uni->hw->formats;

	return 0;
}

static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = {
	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};

static int sti_uniperiph_probe(struct platform_device *pdev)
{
	struct sti_uniperiph_data *priv;
	struct device_node *node = pdev->dev.of_node;
	int ret;

	/* Allocate the private data and the CPU_DAI array */
	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL);
	if (!priv->dai)
		return -ENOMEM;

	priv->pdev = pdev;

	ret = sti_uniperiph_cpu_dai_of(node, priv);

	dev_set_drvdata(&pdev->dev, priv);

	ret = devm_snd_soc_register_component(&pdev->dev,
					      &sti_uniperiph_dai_component,
					      priv->dai, 1);
	if (ret < 0)
		return ret;

	return devm_snd_dmaengine_pcm_register(&pdev->dev,
					       &dmaengine_pcm_config, 0);
}

static struct platform_driver sti_uniperiph_driver = {
	.driver = {
		.name = "sti-uniperiph-dai",
		.of_match_table = snd_soc_sti_match,
	},
	.probe = sti_uniperiph_probe,
};
module_platform_driver(sti_uniperiph_driver);

MODULE_DESCRIPTION("uniperipheral DAI driver");
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
MODULE_LICENSE("GPL v2");

{'repo_name': 'zhovner/OneFileLinux', 'stars': '405', 'repo_language': 'C', 'file_name': 'Kconfig', 'mime_type': 'text/plain', 'hash': 2694575041649580837, 'source_dataset': 'data'}


function only_leading () {
    // 1.0. unreachable leading comment
    var i = 0;  // 1.1. trailing var decl
    // 1.2. leading comment
    do {
        i += 1;
    } while (i < 3);
}

function leading_n_trailing() {
    var i = 0;
    // 2.1. leading comment
    do {
        i += 1;
    } while (i < 3) /* 2.2. trailing comment */;

    // 2.3. trailing comment
}

function only_trailing() {
    var i = 0;
    do {
        i += 1;
    } /* 3.0. pre-keyword trailing comment */ while /* 3.1. pre-cond trailing comment */ (i< 3) /* 3.2. past-cond trailing comment */; /* 3.3 past semicolon trailing */

    // 3.4. trailing comment
}

function implicit_semicolon() {
    do {} while (true) /* 4.1 Trailing */
    /* 4.2 Leading */ do {} while (true) /* 4.3 Trailing */
}

{'repo_name': 'facebook/flow', 'stars': '20773', 'repo_language': 'OCaml', 'file_name': 'Dockerfile', 'mime_type': 'text/plain', 'hash': 8089498931401149307, 'source_dataset': 'data'}


{'repo_name': 'vvakame/typescript-formatter', 'stars': '504', 'repo_language': 'TypeScript', 'file_name': 'settings.json', 'mime_type': 'text/plain', 'hash': -5839524431607392748, 'source_dataset': 'data'}


# Upgrading Guide

## V4 to V5

### Introduction

Version 5 bumps to PHP 7.1+, and adds some additional parameter typing. There have been some internal changes and refactorings too, but nothing that changes the overall feel and usage of the package. The Dotenv class itself is largely unchanged from V4.

Release notes for 5.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v5.0.0).

### Details

1. The `Dotenv\Dotenv::createImmutable` and `Dotenv\Dotenv::createMutable` methods no longer call will result in `getenv` and `putenv` being called. One should instead use `Dotenv\Dotenv::createUnsafeImmutable` and `Dotenv\Dotenv::createUnsafeMutable` methods if one really needs these functions.
2. The `Dotenv\Dotenv` constructor has been modified to expect exactly 4 parameters: a store, a parser, a loader, and a repository. This likely only affect many people, since it is more common to construct this class via the public static create methods. Those methods have not changed.
3. Scalar typehints have been added to the public interface.
4. The parser now returns a result type instead of raising an exception. This change is strictly internal, and most users won't notice a differnce. The responsibility for rasising an exception has simply been shifted up to the caller.
5. Adapters have been refactored again, with changes to the repositories. In particular, the repository builder has been tweaked. It now expects to be explicitly told if you want to use the default adapters or not, and expects individual readers and writers to be added, one by one. Similar changes have been applied to the store factory. Moreover, the `ApacheAdapter` has been changed so that it behaves much like the other adapters. The old behaviour can be simulated by composing it with the new `ReplacingWriter` (see below). We will no longer include this adapter in our default setup, so that people can enable exactly what they need. Finally, by default, we will no longer be using the `PutenvAdapter`. It can be added, as required.
6. Variable whitelisting has been replaced with allow listing, and the responsibility has moved from the loader to a new adapter `GuardedWriter`.
7. The parser has been moved to its own namespace and parses entire files now. This change is expected to have little impact when upgrading. The `Lines` class has also moved to the parser namespace.
8. The loader now only returns the variables that were actually loaded into the repository, and not all the variables from the file. Moreover, it now expects as input the result of running the new parser (an array of entries), rather than raw file content.

The changes listed in (4) mean that instead of:

```php
$repository = Dotenv\Repository\RepositoryBuilder::create()
    ->withReaders([
        new Dotenv\Repository\Adapter\EnvConstAdapter(),
    ])
    ->withWriters([
        new Dotenv\Repository\Adapter\EnvConstAdapter(),
        new Dotenv\Repository\Adapter\PutenvAdapter(),
    ])
    ->make();
```

one would now write:

```php
$repository = Dotenv\Repository\RepositoryBuilder::createWithNoAdapters()
    ->addAdapter(Dotenv\Repository\Adapter\EnvConstAdapter::class)
    ->addWriter(Dotenv\Repository\Adapter\PutenvAdapter::class)
    ->make();
```

Instead of passing class names, one can also pass actual adapter instances. Note that it is not possible to directly construct any of the adapters. One has to go via their static `create` method which returns an optional. This is to strictly encapsulate the fact that not all adapters are capable of running on all systems, and so those that cannot be run, cannot be created. For example, the apache adapter can only be run within an apache web server context. Passing the class names as in the above example will handle this for you, by adding the adapter only if it can be created (the optional has a value set).

To add an apache environment variable writer that only writes to existing apache environment variables, as was the default in v4, one should do the following:

```php
$builder = Dotenv\Repository\RepositoryBuilder::createWithDefaultAdapters();

Dotenv\Repository\Adapter\ApacheAdapter::create()->map(function ($adapter) {
    return new Dotenv\Repository\Adapter\ReplacingWriter($adapter, $adapter);
})->map([$builder, 'addWriter'])->getOrElse($builder);

$repository = $builder->make();
```

The use of optionals handles the case where the apache environment functions are not available (such as in a CLI environment).

## V4.0 to V4.1

### Introduction

Version 4.1 is a minor release, and as such, there are no breaking changes. There is, however a deprecation to be noted.

### Details

The `Dotenv\Dotenv` constructor now expects either an array of file paths as the third parameter, or an instance of `Dotenv\Store\StoreInterface`. Passing an array is deprecated, and will be removed in V5.

## V3 to V4

### Introduction

Version 4 sees some refactoring, and support for escaping dollars in values (https://github.com/vlucas/phpdotenv/pull/380). It is no longer possible to change immutability on the fly, and the `Loader` no longer is responsible for tracking immutability. It is now the responsibility of "repositories" to track this. One must explicitly decide if they want (im)mutability when constructing an instance of `Dotenv\Dotenv`.

Release notes for 4.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v4.0.0).

### Details

V4 has again changed the way you initialize the `Dotenv` class. If you want immutable loading of environment variables, then replace `Dotenv::create` with `Dotenv::createImmutable`, and if you want mutable loading, replace `Dotenv::create` with `Dotenv::createMutable` and `->overload()` with `->load()`. The `overload` method has been removed in faviour of specifying mutability at object construction.

The behaviour when parsing single quoted strings has now changed, to mimic the behaviour of bash. It is no longer possible to escape characters in single quoted strings, and everything is treated literally. As soon as the first single quote character is read, after the initial one, then the variable is treated as ending immediately at that point. When parsing unquoted or double quoted strings, it is now possible to escape dollar signs, to forcefully avoid variable interpolation. Escaping dollars is not mandated, in the sense that if a dollar is present, and not following by variable interpolation sytnax, this is allowed, and the dollar will be treated as a literal dollar. Finally, interpolation of variables is now performed right to left, instead of left to right, so it is possible to nest interpolations to allow using the value of a variable as the name of another for further interpolation.

The `getEnvironmentVariableNames` method is no longer available. This is because calls to `load()` (since v3.0.0) return an associative array of what was loaded, so `$dotenv->getEnvironmentVariableNames()` can be replaced with `array_keys($dotenv->load())`.

There have been various internal refactorings. Appart from what has already been mentioned, the only other changes likely to affect developers is:

1. The `Dotenv\Environment` namespace has been moved to `Dotenv\Repository`, the `Dotenv\Environment\Adapter\AdapterInterface` interface has been replaced by `Dotenv\Repository\Adapter\ReaderInterface` and `Dotenv\Repository\Adapter\WriterInterface`.
2. The `Dotenv\Environment\DotenvFactory` has been (roughly) replaced by `Dotenv\Repository\RepositoryBuilder`, and `Dotenv\Environment\FactoryInterface` has been deleted.
3. `Dotenv\Environment\AbstractVariables` has been replaced by `Dotenv\Repository\AbstractRepository`, `Dotenv\Environment\DotenvVariables` has been replaced by `Dotenv\Repository\AdapterRepository`, and `Dotenv\Environment\VariablesInterface` has been replaced by `Dotenv\Repository\RepositoryInterface`.
4. The `Dotenv\Loader` class has been moved to `Dotenv\Loader\Loader`, and now has a different public interface. It no longer expects any parameters at construction, and implements only the new interface `Dotenv\Loader\LoaderInterface`. Its reponsibility has changed to purely taking raw env file content, and handing it off to the parser, dealing with variable interpolation, and sending off instructions to the repository to set variables. No longer can it be used as a way to read the environment by callers, and nor does it track immutability.
5. The `Dotenv\Parser` and `Dotenv\Lines` classes have moved to `Dotenv\Loader\Parser` and `Dotenv\Loader\Lines`, respectively. `Dotenv\Loader\Parser::parse` now return has either `null` or `Dotenv\Loader\Value` objects as values, instead of `string`s. This is to support the new variable interpolation and dollar escaping features.
6. The `Dotenv\Validator` constructor has changed from `__construct(array $variables, Loader $loader, $required = true)` to `__construct(RepositoryInterface $repository, array $variables, $required = true)`.

The example at the bottom of the below upgrading guide, in V4 now looks like:

```php
<?php

use Dotenv\Dotenv;
use Dotenv\Repository\Adapter\EnvConstAdapter;
use Dotenv\Repository\Adapter\ServerConstAdapter;
use Dotenv\Repository\RepositoryBuilder;

$adapters = [
	new EnvConstAdapter(),
	new ServerConstAdapter(),
];

$repository = RepositoryBuilder::create()
    ->withReaders($adapters)
    ->withWriters($adapters)
    ->immutable()
    ->make();

Dotenv::create($repository, $path, null)->load();
```

Since v3.2.0, it was easily possible to read a file and process variable interpolations, without actually "loading" the variables. This is still possible in v4.0.0. Example code that does this is as follows:

```php
<?php

use Dotenv\Repository\Adapter\ArrayAdapter;
use Dotenv\Repository\RepositoryBuilder;
use Dotenv\Loader\Loader;

$adapters = [new ArrayAdapter()];

$repository = RepositoryBuilder::create()
    ->withReaders($adapters)
    ->withWriters($adapters)
    ->make();

$variables = (new Loader())->load($repository, $content);
```

Notice, that compared to v3, the loader no longer expects file paths in the constructor. Reading of the files is now managed by the `Dotenv\Dotenv` class. The loader is geuinely just loading the content into the repository.

Finally, we note that the minimum supported version of PHP has increased to 5.5.9, up from 5.4.0 in V3 and 5.3.9 in V2.

## V2 to V3

### Introduction

New in Version 3 is first-class support for multiline variables ([#301](https://github.com/vlucas/phpdotenv/pull/301)) and much more flexibility in terms of which parts of the environment we try to read and modify ([#300](https://github.com/vlucas/phpdotenv/pull/300)). Consequently, you will need to replace any occurrences of `new Dotenv(...)` with `Dotenv::create(...)`, since our new native constructor takes a `Loader` instance now, so that it can be truly customized if required. Finally, one should note that the loader will no longer be trimming values ([#302](https://github.com/vlucas/phpdotenv/pull/302)), moreover `Loader::load()` and its callers now return an associative array of the variables loaded with their values, rather than an array of raw lines from the environment file ([#306](https://github.com/vlucas/phpdotenv/pull/306)).

Release notes for 3.0.0 are available [here](https://github.com/vlucas/phpdotenv/releases/tag/v3.0.0).

### Details

V3 has changed the way you initialize the `Dotenv` class. Consequently, you will need to replace any occurrences of new Dotenv(...) with Dotenv::create(...), since our new native constructor takes a `Loader` instance now.

`Loader::load()` and its callers now return an associative array of the variables loaded with their values.

Value parsing has been modified in the following ways:

1. For unquoted strings, as soon as there's a hash, it's treated as a comment start.
2. We're being stricter about invalid escape sequences within quoted strings.
3. We're no longer trimming the parsed values of quoted strings.
4. Multiline quoted values are now permitted, and will be parsed by V3.

| input value | V2.5.2 | V2.6.1 | V3.3.1 |
|-|-|-|-|
| `foo#bar` | `foo#bar` | `foo#bar` | `foo` |
| `foo # bar` | `foo` | `foo` | `foo` |
| `"iiiiviiiixiiiiviiii\n"` | silent failure | `iiiviiiixiiiiviiii\n` | fails with invalid escape sequence exception |
| `"iiiiviiiixiiiiviiii\\n"` | `iiiiviiiixiiiiviiii\n` | `iiiiviiiixiiiiviiii\n` | `iiiiviiiixiiiiviiii\n` |
| `"foo\"bar"` | `foo"bar` | `foo"bar` | `foo"bar` |
| `"  foo "` | `foo` with whitespace trimmed | `foo` with whitespace trimmed | `foo` with 2 spaces in front and one after |

In double quoted strings, double quotes and backslashes need escaping with a backslash, and in single quoted strings, single quote and backslashes need escaping with a backslash. In v2.5.2, forgetting an escape can lead to odd results due to the regex running out of stack, but this was fixed in 2.6 and 3.3, with 2.6 allowing you to continue after an unescaped backslash, but 3.3 not.

It's possible to use phpdotenv V3 in a threaded environment, instructing it to not call any functions that are not tread-safe:

```php
<?php

use Dotenv\Dotenv;
use Dotenv\Environment\Adapter\EnvConstAdapter;
use Dotenv\Environment\Adapter\ServerConstAdapter;
use Dotenv\Environment\DotenvFactory;

$factory = new DotenvFactory([new EnvConstAdapter(), new ServerConstAdapter()]);

Dotenv::create($path, null, $factory)->load();
```

Finally, we note that the minimum supported version of PHP has increased from 5.3.9 to 5.4.0.

{'repo_name': 'vlucas/phpdotenv', 'stars': '10591', 'repo_language': 'PHP', 'file_name': 'LexerTest.php', 'mime_type': 'text/x-php', 'hash': -1233995842570651700, 'source_dataset': 'data'}


options
{
	language = "CSharp";
}

class DataParser extends Parser;

file:	(	sh:SHORT	{Console.Out.WriteLine(sh.getText());}
		|	st:STRING	{Console.Out.WriteLine(@""""+st.getText()+@"""");}
		)+
	;

class DataLexer extends Lexer;
options {
	charVocabulary = '\u0000'..'\u00FF';
}

SHORT
	:	'\u0000' high:. lo:.
		{
		int v = (((int)high)<<8) + lo;
		$setText(""+v);
		}
	;

STRING
	:	'\u0001'!	// begin string (discard)
		( ~'\u0002' )*
		'\u0002'!	// end string (discard)
	;

{'repo_name': 'boo-lang/boo', 'stars': '668', 'repo_language': 'C#', 'file_name': 'booi.1', 'mime_type': 'text/troff', 'hash': -6449243745090603225, 'source_dataset': 'data'}