001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import static org.junit.jupiter.api.Assertions.assertFalse;
021import static org.junit.jupiter.api.Assertions.assertInstanceOf;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023
024import java.io.File;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import org.apache.hadoop.hbase.testclassification.MiscTests;
028import org.apache.hadoop.hbase.testclassification.SmallTests;
029import org.junit.jupiter.api.Tag;
030import org.junit.jupiter.api.Test;
031import org.junit.jupiter.api.io.TempDir;
032
033@Tag(MiscTests.TAG)
034@Tag(SmallTests.TAG)
035public class TestProfilerBackend {
036
037  @TempDir
038  Path tempDir;
039
040  @Test
041  public void testDetectReturnsLibraryBackendWhenLibraryOnClasspath() {
042    // async-profiler is on the test classpath, so detect() always returns LibraryBackend
043    // regardless of home setting — library takes priority.
044    ProfilerBackend backend = ProfilerBackend.detect(null);
045    assertNotNull(backend);
046    assertInstanceOf(LibraryBackend.class, backend);
047  }
048
049  @Test
050  public void testDetectReturnsBinaryBackendWhenHomeSet() throws Exception {
051    // Create a fake profiler home with bin/asprof so path check passes
052    Files.createDirectories(tempDir.resolve("bin"));
053    Files.createFile(tempDir.resolve("bin").resolve("asprof"));
054
055    // The test classpath has the async-profiler JAR (optional compile dep), so detect() returns
056    // LibraryBackend here. BinaryBackend selection is verified under isolation by
057    // TestProfilerBackendIsolated.testDetectReturnsBinaryBackendWhenLibraryAbsentButHomeSet.
058    // This test simply asserts that a valid home always yields a non-null backend.
059    assertNotNull(ProfilerBackend.detect(tempDir.toString()));
060  }
061
062  @Test
063  public void testBinaryBackendDetectReturnsNonNullWhenHomeProvided() {
064    // Any non-empty home string produces a non-null backend (LibraryBackend when JAR is present,
065    // BinaryBackend when absent). Both are valid — what matters is non-null.
066    assertNotNull(ProfilerBackend.detect("/fake/profiler/home"));
067  }
068
069  @Test
070  public void testDetectPrefersLibraryWhenBothAvailable() {
071    // Library takes priority over binary home. Since the JAR is on the test classpath,
072    // detect() must return LibraryBackend even when a home is provided.
073    ProfilerBackend backend = ProfilerBackend.detect("/some/home");
074    assertNotNull(backend);
075    assertInstanceOf(LibraryBackend.class, backend);
076  }
077
078  @Test
079  public void testBinaryBackendDestroyDoesNotThrowWhenNoProcess() {
080    BinaryBackend backend = new BinaryBackend("/fake/home");
081    // Should not throw when no process has been started
082    backend.destroy();
083  }
084
085  @Test
086  public void testBinaryBackendExecuteStartFailureIsNotNullPointerException() throws Exception {
087    // BinaryBackend.executeStart must surface failures as IOException or RuntimeException,
088    // never NullPointerException. The null-PID guard at BinaryBackend.java:116-118 cannot be
089    // triggered without mocking ProcessUtils.getPid() (static method), but we verify the
090    // adjacent failure path: a non-existent profiler home causes ProcessBuilder.start() to fail,
091    // which runCmdAsync wraps in IllegalStateException. This confirms the error path goes through
092    // the expected exception type and not an unguarded NPE from pid.toString().
093    BinaryBackend backend = new BinaryBackend("/fake/home/that/does/not/exist");
094    ProfileServlet servlet = new ProfileServlet(null);
095    javax.servlet.http.HttpServletRequest req =
096      org.mockito.Mockito.mock(javax.servlet.http.HttpServletRequest.class);
097    org.mockito.Mockito.when(req.getParameterMap()).thenReturn(java.util.Collections.emptyMap());
098    org.mockito.Mockito.when(req.getParameter(org.mockito.Mockito.anyString())).thenReturn(null);
099    ProfileServlet.ProfileRequest profileReq = servlet.parseProfileRequest(req);
100    Throwable caught = null;
101    try {
102      backend.executeStart(profileReq, File.createTempFile("test", ".html"));
103    } catch (Exception e) {
104      caught = e;
105    }
106    assertNotNull(caught, "executeStart must throw when profiler home is missing");
107    assertFalse(caught instanceof NullPointerException,
108      "executeStart must not throw NullPointerException; got: " + caught.getClass().getName());
109  }
110}