|
|
|
|
@@ -1,4 +1,274 @@
|
|
|
|
|
package com.github.gtache.autosubtitle.setup.ffmpeg;
|
|
|
|
|
|
|
|
|
|
import com.github.gtache.autosubtitle.archive.Archiver;
|
|
|
|
|
import com.github.gtache.autosubtitle.archive.ArchiverProvider;
|
|
|
|
|
import com.github.gtache.autosubtitle.impl.Architecture;
|
|
|
|
|
import com.github.gtache.autosubtitle.impl.OS;
|
|
|
|
|
import com.github.gtache.autosubtitle.process.ProcessResult;
|
|
|
|
|
import com.github.gtache.autosubtitle.process.ProcessRunner;
|
|
|
|
|
import com.github.gtache.autosubtitle.setup.SetupException;
|
|
|
|
|
import com.github.gtache.autosubtitle.setup.SetupStatus;
|
|
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
|
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
|
|
|
import org.mockito.ArgumentCaptor;
|
|
|
|
|
import org.mockito.Mock;
|
|
|
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.net.http.HttpClient;
|
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
|
import java.net.http.HttpResponse;
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
import static java.util.Objects.requireNonNull;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
import static org.mockito.ArgumentMatchers.anyList;
|
|
|
|
|
import static org.mockito.Mockito.*;
|
|
|
|
|
|
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
|
|
|
class TestFFmpegSetupManager {
|
|
|
|
|
|
|
|
|
|
private final FFmpegSetupConfiguration configuration;
|
|
|
|
|
private final ArchiverProvider archiverProvider;
|
|
|
|
|
private final Archiver archiver;
|
|
|
|
|
private final ProcessRunner processRunner;
|
|
|
|
|
private final HttpClient httpClient;
|
|
|
|
|
private final HttpResponse<Path> response;
|
|
|
|
|
private final FFmpegSetupManager setupManager;
|
|
|
|
|
private final ProcessResult processResult;
|
|
|
|
|
private final Path systemPath;
|
|
|
|
|
private final Path ffmpegInstallerPath;
|
|
|
|
|
private final Path ffprobeInstallerPath;
|
|
|
|
|
private final Path rootPath;
|
|
|
|
|
|
|
|
|
|
TestFFmpegSetupManager(@Mock final FFmpegSetupConfiguration configuration, @Mock final ArchiverProvider archiverProvider,
|
|
|
|
|
@Mock final Archiver archiver, @Mock final ProcessRunner processRunner,
|
|
|
|
|
@Mock final HttpClient httpClient, @Mock final ProcessResult processResult, @Mock final HttpResponse<Path> response) {
|
|
|
|
|
|
|
|
|
|
this.configuration = requireNonNull(configuration);
|
|
|
|
|
this.archiverProvider = requireNonNull(archiverProvider);
|
|
|
|
|
this.archiver = requireNonNull(archiver);
|
|
|
|
|
this.processRunner = requireNonNull(processRunner);
|
|
|
|
|
this.httpClient = requireNonNull(httpClient);
|
|
|
|
|
this.response = requireNonNull(response);
|
|
|
|
|
this.setupManager = new FFmpegSetupManager(configuration, archiverProvider, processRunner, httpClient);
|
|
|
|
|
this.processResult = requireNonNull(processResult);
|
|
|
|
|
this.systemPath = Paths.get("system");
|
|
|
|
|
this.ffmpegInstallerPath = Paths.get("ffmpeg.sh");
|
|
|
|
|
this.ffprobeInstallerPath = Paths.get("ffprobe.sh");
|
|
|
|
|
this.rootPath = Paths.get("root");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void beforeEach() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.systemFFmpegPath()).thenReturn(systemPath);
|
|
|
|
|
when(configuration.root()).thenReturn(rootPath);
|
|
|
|
|
when(configuration.ffmpegInstallerPath()).thenReturn(ffmpegInstallerPath);
|
|
|
|
|
when(configuration.ffprobeInstallerPath()).thenReturn(ffprobeInstallerPath);
|
|
|
|
|
when(archiverProvider.getArchiver("sh")).thenReturn(archiver);
|
|
|
|
|
when(archiverProvider.getArchiver("tar")).thenReturn(archiver);
|
|
|
|
|
when(processRunner.run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5))).thenReturn(processResult);
|
|
|
|
|
when(httpClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(response);
|
|
|
|
|
when(response.statusCode()).thenReturn(200);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testName() {
|
|
|
|
|
assertEquals("FFmpeg", setupManager.name());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testGetStatusSystemFalse() throws IOException, SetupException {
|
|
|
|
|
final var ffmpegPath = Paths.get("path");
|
|
|
|
|
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
|
|
|
|
when(processResult.exitCode()).thenReturn(1);
|
|
|
|
|
when(processRunner.run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5))).thenReturn(processResult);
|
|
|
|
|
assertEquals(SetupStatus.NOT_INSTALLED, setupManager.getStatus());
|
|
|
|
|
verify(processRunner).run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testGetStatusSystemException() throws IOException, SetupException {
|
|
|
|
|
final var ffmpegPath = Paths.get("path");
|
|
|
|
|
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
|
|
|
|
when(processRunner.run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5))).thenThrow(IOException.class);
|
|
|
|
|
assertThrows(SetupException.class, setupManager::getStatus);
|
|
|
|
|
verify(processRunner).run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testGetStatusSystemTrue() throws IOException, SetupException {
|
|
|
|
|
final var ffmpegPath = Paths.get("path");
|
|
|
|
|
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
|
|
|
|
when(processRunner.run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5))).thenReturn(processResult);
|
|
|
|
|
assertEquals(SetupStatus.SYSTEM_INSTALLED, setupManager.getStatus());
|
|
|
|
|
verify(processRunner).run(List.of(systemPath.toString(), "-version"), Duration.ofSeconds(5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testGetStatusBundledFalse(@TempDir final Path tempDir) throws IOException, SetupException {
|
|
|
|
|
final var ffmpegPath = tempDir.resolve("ffmpeg");
|
|
|
|
|
Files.deleteIfExists(ffmpegPath);
|
|
|
|
|
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
|
|
|
|
when(processResult.exitCode()).thenReturn(1);
|
|
|
|
|
when(processRunner.run(anyList(), any())).thenReturn(processResult);
|
|
|
|
|
assertEquals(SetupStatus.NOT_INSTALLED, setupManager.getStatus());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testGetStatusBundledTrue(@TempDir final Path tempDir) throws IOException, SetupException {
|
|
|
|
|
final var ffmpegPath = tempDir.resolve("ffmpeg");
|
|
|
|
|
Files.createFile(ffmpegPath);
|
|
|
|
|
when(configuration.bundledFFmpegPath()).thenReturn(ffmpegPath);
|
|
|
|
|
when(processResult.exitCode()).thenReturn(1);
|
|
|
|
|
when(processRunner.run(anyList(), any())).thenReturn(processResult);
|
|
|
|
|
assertEquals(SetupStatus.BUNDLE_INSTALLED, setupManager.getStatus());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallWindowsDecompressError() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.WINDOWS);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.UNKNOWN);
|
|
|
|
|
doThrow(IOException.class).when(archiver).decompress(ffmpegInstallerPath, rootPath);
|
|
|
|
|
assertThrows(SetupException.class, setupManager::install);
|
|
|
|
|
verify(archiver).decompress(ffmpegInstallerPath, rootPath);
|
|
|
|
|
checkURLRequested("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallWindows() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.WINDOWS);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.UNKNOWN);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(ffmpegInstallerPath, rootPath);
|
|
|
|
|
checkURLRequested("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxAMD64() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.AMD64);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(eq(ffmpegInstallerPath), any());
|
|
|
|
|
verify(archiver).decompress(any(), eq(rootPath));
|
|
|
|
|
checkURLRequested("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxARM64() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.ARM64);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(eq(ffmpegInstallerPath), any());
|
|
|
|
|
verify(archiver).decompress(any(), eq(rootPath));
|
|
|
|
|
checkURLRequested("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxI686() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.I686);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(eq(ffmpegInstallerPath), any());
|
|
|
|
|
verify(archiver).decompress(any(), eq(rootPath));
|
|
|
|
|
checkURLRequested("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-i686-static.tar.xz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxARMHF() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.ARMHF);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(eq(ffmpegInstallerPath), any());
|
|
|
|
|
verify(archiver).decompress(any(), eq(rootPath));
|
|
|
|
|
checkURLRequested("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-armhf-static.tar.xz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxARMEL() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.ARMEL);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(eq(ffmpegInstallerPath), any());
|
|
|
|
|
verify(archiver).decompress(any(), eq(rootPath));
|
|
|
|
|
checkURLRequested("https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-armel-static.tar.xz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallLinuxUnsupportedArchitecture() {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.LINUX);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.UNKNOWN);
|
|
|
|
|
assertThrows(SetupException.class, setupManager::install);
|
|
|
|
|
verifyNoInteractions(httpClient, archiver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallMacAMD64() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.MAC);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.AMD64);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(ffmpegInstallerPath, rootPath);
|
|
|
|
|
verify(archiver).decompress(ffprobeInstallerPath, rootPath);
|
|
|
|
|
final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class);
|
|
|
|
|
verify(httpClient, times(2)).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class));
|
|
|
|
|
final var request = requestCapture.getAllValues();
|
|
|
|
|
assertTrue(request.stream().anyMatch(r -> r.uri().toString().equals("https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip")));
|
|
|
|
|
assertTrue(request.stream().anyMatch(r -> r.uri().toString().equals("https://evermeet.cx/ffmpeg/getrelease/ffprobe/zip")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallMacARM64() throws IOException, InterruptedException {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.MAC);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.ARM64);
|
|
|
|
|
assertDoesNotThrow(setupManager::install);
|
|
|
|
|
verify(archiver).decompress(ffmpegInstallerPath, rootPath);
|
|
|
|
|
verify(archiver).decompress(ffprobeInstallerPath, rootPath);
|
|
|
|
|
final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class);
|
|
|
|
|
verify(httpClient, times(2)).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class));
|
|
|
|
|
final var request = requestCapture.getAllValues();
|
|
|
|
|
assertTrue(request.stream().anyMatch(r -> r.uri().toString().equals("https://www.osxexperts.net/ffmpeg7arm.zip")));
|
|
|
|
|
assertTrue(request.stream().anyMatch(r -> r.uri().toString().equals("https://www.osxexperts.net/ffprobe7arm.zip")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallMacUnsupportedArchitecture() {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.MAC);
|
|
|
|
|
when(configuration.architecture()).thenReturn(Architecture.ARM32);
|
|
|
|
|
assertThrows(SetupException.class, setupManager::install);
|
|
|
|
|
verifyNoInteractions(httpClient, archiver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testInstallUnknownOS() {
|
|
|
|
|
when(configuration.os()).thenReturn(OS.UNKNOWN);
|
|
|
|
|
assertThrows(SetupException.class, setupManager::install);
|
|
|
|
|
verifyNoInteractions(httpClient, archiver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void testUninstall(@TempDir final Path tempDir) throws SetupException, IOException {
|
|
|
|
|
final var subdir = tempDir.resolve("subfolder");
|
|
|
|
|
Files.createDirectories(subdir);
|
|
|
|
|
final var subpath = subdir.resolve("subfile");
|
|
|
|
|
Files.createFile(subpath);
|
|
|
|
|
final var file = tempDir.resolve("rootfile");
|
|
|
|
|
Files.createFile(file);
|
|
|
|
|
when(configuration.root()).thenReturn(tempDir);
|
|
|
|
|
setupManager.uninstall();
|
|
|
|
|
assertFalse(Files.exists(tempDir));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void checkURLRequested(final String url) throws IOException, InterruptedException {
|
|
|
|
|
final var requestCapture = ArgumentCaptor.forClass(HttpRequest.class);
|
|
|
|
|
verify(httpClient).send(requestCapture.capture(), any(HttpResponse.BodyHandler.class));
|
|
|
|
|
final var request = requestCapture.getValue();
|
|
|
|
|
assertEquals(url, request.uri().toString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|