/*******************************************************************************
 * Copyright (c) 2015 EfficiOS Inc., Alexandre Montplaisir
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/

package org.eclipse.tracecompass.lttng2.ust.core.tests.analysis.debuginfo;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoAnalysisModule;
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoBinaryAspect;
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoBinaryFile;
import org.eclipse.tracecompass.lttng2.ust.core.analysis.debuginfo.UstDebugInfoLoadedBinaryFile;
import org.eclipse.tracecompass.lttng2.ust.core.tests.shared.LttngUstTestTraceUtils;
import org.eclipse.tracecompass.lttng2.ust.core.trace.LttngUstEvent;
import org.eclipse.tracecompass.lttng2.ust.core.trace.LttngUstTrace;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfAnalysisException;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * Tests for the {@link UstDebugInfoAnalysisModule}
 *
 * @author Alexandre Montplaisir
 */
public class UstDebugInfoAnalysisModuleTest {

    private static final @NonNull CtfTestTrace REAL_TEST_TRACE = CtfTestTrace.DEBUG_INFO4;
    private static final @NonNull CtfTestTrace SYNTH_EXEC_TRACE = CtfTestTrace.DEBUG_INFO_SYNTH_EXEC;
    private static final @NonNull CtfTestTrace SYNTH_TWO_PROCESSES_TRACE = CtfTestTrace.DEBUG_INFO_SYNTH_TWO_PROCESSES;
    private static final @NonNull CtfTestTrace SYNTH_BUILDID_DEBUGLINK_TRACE = CtfTestTrace.DEBUG_INFO_SYNTH_BUILDID_DEBUGLINK;
    private static final @NonNull CtfTestTrace INVALID_TRACE = CtfTestTrace.CYG_PROFILE;

    private UstDebugInfoAnalysisModule fModule;

    /**
     * Test setup
     */
    @Before
    public void setup() {
        fModule = new UstDebugInfoAnalysisModule();
    }

    /**
     * Test cleanup
     */
    @After
    public void tearDown() {
        fModule.dispose();
        fModule = null;
    }

    /**
     * Test for {@link UstDebugInfoAnalysisModule#getAnalysisRequirements()}
     */
    @Test
    public void testGetAnalysisRequirements() {
        Iterable<TmfAbstractAnalysisRequirement> requirements = fModule.getAnalysisRequirements();
        assertNotNull(requirements);
        assertTrue(Iterables.isEmpty(requirements));
    }

    /**
     * Test that the analysis can execute on a valid trace.
     */
    @Test
    public void testCanExecute() {
        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(REAL_TEST_TRACE);
        assertTrue(fModule.canExecute(trace));
        LttngUstTestTraceUtils.dispose(REAL_TEST_TRACE);
    }

    /**
     * Test that the analysis correctly refuses to execute on an invalid trace
     * (LTTng-UST < 2.8 in this case).
     */
    @Test
    public void testCannotExcecute() {
        LttngUstTrace invalidTrace = LttngUstTestTraceUtils.getTrace(INVALID_TRACE);
        assertFalse(fModule.canExecute(invalidTrace));
        LttngUstTestTraceUtils.dispose(INVALID_TRACE);
    }

    private void executeModule(ITmfTrace trace) {
        assertNotNull(trace);
        try {
            fModule.setTrace(trace);
        } catch (TmfAnalysisException e) {
            fail();
        }
        fModule.schedule();
        fModule.waitForCompletion();
    }

    /**
     * Test that basic execution of the module works well.
     */
    @Test
    public void testExecution() {
        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(REAL_TEST_TRACE);
        executeModule(trace);
        ITmfStateSystem ss = fModule.getStateSystem();
        assertNotNull(ss);
        LttngUstTestTraceUtils.dispose(REAL_TEST_TRACE);
    }

    /**
     * Test that the binary callsite aspect resolves correctly for some
     * user-defined tracepoints in the trace.
     *
     * These should be available even without the binaries with debug symbols
     * being present on the system.
     */
    @Test
    public void testBinaryCallsites() {
        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(REAL_TEST_TRACE);

        /*
         * Fake a "trace opened" signal, so that the relevant analyses are
         * started.
         */
        TmfTraceOpenedSignal signal = new TmfTraceOpenedSignal(this, trace, null);
        TmfSignalManager.dispatchSignal(signal);

        UstDebugInfoAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, UstDebugInfoAnalysisModule.class, UstDebugInfoAnalysisModule.ID);
        assertNotNull(module);
        module.waitForCompletion();

        /* Send a request to get the 3 events we are interested in */
        List<@NonNull LttngUstEvent> events = new ArrayList<>();
        TmfEventRequest request = new TmfEventRequest(LttngUstEvent.class, 31, 1, ExecutionType.FOREGROUND) {
            @Override
            public void handleData(ITmfEvent event) {
                super.handleData(event);
                events.add((LttngUstEvent) event);
            }
        };
        trace.sendRequest(request);
        try {
            request.waitForCompletion();
        } catch (InterruptedException e) {
            fail(e.getMessage());
        }

        /* Tests that the aspects are resolved correctly */
        final UstDebugInfoBinaryAspect aspect = UstDebugInfoBinaryAspect.INSTANCE;

        String actual = checkNotNull(aspect.resolve(events.get(0))).toString();
        String expected = "/home/simark/src/babeltrace/tests/debug-info-data/libhello_so+0x14d4";
        assertEquals(expected, actual);

        LttngUstTestTraceUtils.dispose(REAL_TEST_TRACE);
    }

    /**
     * Test the analysis with a test trace doing an "exec" system call.
     */
    @Test
    public void testExec() {
        UstDebugInfoLoadedBinaryFile matchingFile, expected;

        int vpid = 1337;

        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(SYNTH_EXEC_TRACE);
        executeModule(trace);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo", null, null, false);
        matchingFile = fModule.getMatchingFile(4000000, vpid, 0x400100);
        assertEquals(expected, matchingFile);

        expected = null;
        matchingFile = fModule.getMatchingFile(8000000, vpid, 0x400100);
        assertEquals(expected, matchingFile);

        expected = new UstDebugInfoLoadedBinaryFile(0x500000, "/tmp/bar", null, null, false);
        matchingFile = fModule.getMatchingFile(9000000, vpid, 0x500100);
        assertEquals(expected, matchingFile);

        LttngUstTestTraceUtils.dispose(SYNTH_EXEC_TRACE);
    }

    /**
     * Test the analysis with a test trace with two processes doing a statedump
     * simultaneously.
     */
    @Test
    public void testTwoProcesses() {
        UstDebugInfoLoadedBinaryFile matchingFile, expected;
        int vpid1 = 1337;
        int vpid2 = 2001;

        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(SYNTH_TWO_PROCESSES_TRACE);
        executeModule(trace);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "/tmp/debuglink1", false);
        matchingFile = fModule.getMatchingFile(11000000, vpid1, 0x400100);
        assertEquals(expected, matchingFile);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/bar", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "/tmp/debuglink2", false);
        matchingFile = fModule.getMatchingFile(12000000, vpid2, 0x400100);
        assertEquals(expected, matchingFile);

        LttngUstTestTraceUtils.dispose(SYNTH_TWO_PROCESSES_TRACE);
    }


    /**
     * Test the analysis with a trace with debug_link information.
     */
    @Test
    public void testBuildIDDebugLink() {
        UstDebugInfoLoadedBinaryFile matchingFile, expected;

        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(SYNTH_BUILDID_DEBUGLINK_TRACE);
        executeModule(trace);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo_nn", null, null, false);
        matchingFile = fModule.getMatchingFile(17000000, 1337, 0x400100);
        assertEquals(expected, matchingFile);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo_yn", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", null, false);
        matchingFile = fModule.getMatchingFile(18000000, 1338, 0x400100);
        assertEquals(expected, matchingFile);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo_ny", null, "/tmp/debug_link1", false);
        matchingFile = fModule.getMatchingFile(19000000, 1339, 0x400100);
        assertEquals(expected, matchingFile);

        expected = new UstDebugInfoLoadedBinaryFile(0x400000, "/tmp/foo_yy", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "/tmp/debug_link2", false);
        matchingFile = fModule.getMatchingFile(20000000, 1340, 0x400100);
        assertEquals(expected, matchingFile);

        LttngUstTestTraceUtils.dispose(SYNTH_BUILDID_DEBUGLINK_TRACE);
    }

    /**
     * Test the {@link UstDebugInfoAnalysisModule#getAllBinaries} method.
     */
    @Test
    public void testGetAllBinaries() {
        LttngUstTrace trace = LttngUstTestTraceUtils.getTrace(REAL_TEST_TRACE);
        executeModule(trace);

        List<UstDebugInfoBinaryFile> actualBinaries = Lists.newArrayList(fModule.getAllBinaries());
        List<UstDebugInfoBinaryFile> expectedBinaries = Lists.newArrayList(
                new UstDebugInfoBinaryFile("/home/simark/src/babeltrace/tests/debug-info-data/libhello_so",
                                           "cdd98cdd87f7fe64c13b6daad553987eafd40cbb", null, true),
                new UstDebugInfoBinaryFile("/home/simark/src/babeltrace/tests/debug-info-data/test",
                                           "0683255d2cf219c33cc0efd6039db09ccc4416d7", null, false),
                new UstDebugInfoBinaryFile("[linux-vdso.so.1]", null, null, false),
                new UstDebugInfoBinaryFile("/usr/local/lib/liblttng-ust-dl.so.0.0.0",
                                           "39c035014cc02008d6884fcb1be4e020cc820366", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/libdl-2.23.so",
                                           "db3f9be9f4ebe9e2a21e4ae0b4ef7165d40fdfef", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/libc-2.23.so",
                                           "946025a5cad7b5f2dfbaebc6ebd1fcc004349b48", null, true),
                new UstDebugInfoBinaryFile("/usr/local/lib/liblttng-ust.so.0.0.0",
                                           "405b0b15daa73eccb88076247ba30356c00d3b92", null, true),
                new UstDebugInfoBinaryFile("/usr/local/lib/liblttng-ust-tracepoint.so.0.0.0",
                                           "62c028aad38adb5e0910c527d522e8c86a0a3344", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/librt-2.23.so",
                                           "aba676bda7fb6adb71e100159915504e1a0c17e6", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/liburcu-bp.so.4.0.0",
                                           "b9dfadea234107f8453bc636fc160047e0c01b7a", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/liburcu-cds.so.4.0.0",
                                           "420527f6dacc762378d9fa7def54d91c80a6c87e", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/libpthread-2.23.so",
                                           "d91ed99c8425b7ce5da5bb750662a91038e02a78", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/ld-2.23.so",
                                           "524eff0527e923e4adc4be9db1ef7475607b92e8", null, true),
                new UstDebugInfoBinaryFile("/usr/lib/liburcu-common.so.4.0.0",
                                           "f279a6d46a2b846e15e7abd99cfe9fbe8d7f8295", null, true));

        Comparator<UstDebugInfoBinaryFile> comparator = Comparator.comparing(UstDebugInfoBinaryFile::getFilePath);
        actualBinaries.sort(comparator);
        expectedBinaries.sort(comparator);

        /* Highlights failures more easily */
        for (int i = 0; i < expectedBinaries.size(); i++) {
            assertEquals(expectedBinaries.get(i), actualBinaries.get(i));
        }

        assertEquals(actualBinaries, expectedBinaries);

        LttngUstTestTraceUtils.dispose(REAL_TEST_TRACE);
    }

}
