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}