org.ballerinalang.langserver.command.executors.CreateTestExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.ballerinalang.langserver.command.executors.CreateTestExecutor.java

Source

/*
 * Copyright (c) 2018, WSO2 Inc. (http://wso2.com) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ballerinalang.langserver.command.executors;

import com.google.gson.JsonObject;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.ballerinalang.annotation.JavaSPIService;
import org.ballerinalang.langserver.BallerinaLanguageServer;
import org.ballerinalang.langserver.BallerinaWorkspaceService;
import org.ballerinalang.langserver.client.ExtendedLanguageClient;
import org.ballerinalang.langserver.command.ExecuteCommandKeys;
import org.ballerinalang.langserver.command.LSCommandExecutor;
import org.ballerinalang.langserver.command.LSCommandExecutorException;
import org.ballerinalang.langserver.command.testgen.TestGenerator;
import org.ballerinalang.langserver.command.testgen.TestGeneratorException;
import org.ballerinalang.langserver.common.constants.CommandConstants;
import org.ballerinalang.langserver.compiler.DocumentServiceKeys;
import org.ballerinalang.langserver.compiler.LSCompiler;
import org.ballerinalang.langserver.compiler.LSCompilerException;
import org.ballerinalang.langserver.compiler.LSCompilerUtil;
import org.ballerinalang.langserver.compiler.LSContext;
import org.ballerinalang.langserver.compiler.workspace.WorkspaceDocumentManager;
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ResourceOperation;
import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.wso2.ballerinalang.compiler.tree.BLangNode;
import org.wso2.ballerinalang.compiler.tree.BLangPackage;
import org.wso2.ballerinalang.compiler.util.ProjectDirConstants;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

import static org.ballerinalang.langserver.BallerinaWorkspaceService.Experimental.SHOW_TEXT_DOCUMENT;
import static org.ballerinalang.langserver.command.CommandUtil.getBLangNode;

/**
 * Represents the create variable command executor.
 *
 * @since 0.985.0
 */
@JavaSPIService("org.ballerinalang.langserver.command.LSCommandExecutor")
public class CreateTestExecutor implements LSCommandExecutor {

    private static final String COMMAND = "CREATE_TEST";

    public static String generateTestFileName(Path sourceFilePath) {
        String fileName = FilenameUtils.removeExtension(sourceFilePath.toFile().getName());
        return fileName + "_test" + ProjectDirConstants.BLANG_SOURCE_EXT;
    }

    private static ImmutablePair<Path, Path> createTestFolderIfNotExists(Path sourceFilePath) {
        Path projectRoot = Paths.get(LSCompilerUtil.getSourceRoot(sourceFilePath));
        ImmutablePair<Path, Path> testsDirPath = getTestsDirPath(sourceFilePath, projectRoot);

        //Check for tests folder, if not exists create a new folder
        testsDirPath.getRight().toFile().mkdirs();

        return testsDirPath;
    }

    /**
     * Returns a pair of current module path and calculated target test path.
     *
     * @param sourceFilePath source file path
     * @param projectRoot    project root
     * @return a pair of currentModule path(left-side) and target test path(right-side)
     */
    private static ImmutablePair<Path, Path> getTestsDirPath(Path sourceFilePath, Path projectRoot) {
        if (sourceFilePath == null || projectRoot == null) {
            return null;
        }
        Path currentModulePath = projectRoot;
        Path prevSourceRoot = sourceFilePath.getParent();
        List<String> pathParts = new ArrayList<>();
        try {
            while (prevSourceRoot != null) {
                Path newSourceRoot = prevSourceRoot.getParent();
                currentModulePath = prevSourceRoot;
                if (newSourceRoot == null || Files.isSameFile(newSourceRoot, projectRoot)) {
                    // We have reached the project root
                    break;
                }
                pathParts.add(prevSourceRoot.getFileName().toString());
                prevSourceRoot = newSourceRoot;
            }
        } catch (IOException e) {
            // do nothing
        }

        // Append `tests` path
        Path testDirPath = currentModulePath.resolve(ProjectDirConstants.TEST_DIR_NAME);

        // Add same directory structure inside the module
        for (String part : pathParts) {
            testDirPath = testDirPath.resolve(part);
        }

        return new ImmutablePair<>(currentModulePath, testDirPath);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object execute(LSContext context) throws LSCommandExecutorException {
        String docUri = null;
        int line = -1;
        int column = -1;

        for (Object arg : context.get(ExecuteCommandKeys.COMMAND_ARGUMENTS_KEY)) {
            String argKey = ((JsonObject) arg).get(ARG_KEY).getAsString();
            String argVal = ((JsonObject) arg).get(ARG_VALUE).getAsString();
            switch (argKey) {
            case CommandConstants.ARG_KEY_DOC_URI:
                docUri = argVal;
                context.put(DocumentServiceKeys.FILE_URI_KEY, docUri);
                break;
            case CommandConstants.ARG_KEY_NODE_LINE:
                line = Integer.parseInt(argVal);
                break;
            case CommandConstants.ARG_KEY_NODE_COLUMN:
                column = Integer.parseInt(argVal);
                break;
            default:
            }
        }

        if (line == -1 || column == -1 || docUri == null) {
            throw new LSCommandExecutorException("Invalid parameters received for the create test command!");
        }

        WorkspaceDocumentManager docManager = context.get(ExecuteCommandKeys.DOCUMENT_MANAGER_KEY);
        LSCompiler lsCompiler = context.get(ExecuteCommandKeys.LS_COMPILER_KEY);

        // Compile the source file
        BLangPackage builtSourceFile = null;
        try {
            builtSourceFile = lsCompiler.getBLangPackage(context, docManager, false, null, false);
        } catch (LSCompilerException e) {
            throw new LSCommandExecutorException("Couldn't compile the source", e);
        }

        // Generate test file and notify Client
        BallerinaLanguageServer ballerinaLanguageServer = context.get(ExecuteCommandKeys.LANGUAGE_SERVER_KEY);
        ExtendedLanguageClient client = ballerinaLanguageServer.getClient();
        BallerinaWorkspaceService workspace = (BallerinaWorkspaceService) ballerinaLanguageServer
                .getWorkspaceService();
        try {
            if (builtSourceFile == null || builtSourceFile.diagCollector.hasErrors()) {
                String message = "Test generation failed due to compilation errors!";
                if (client != null) {
                    client.showMessage(new MessageParams(MessageType.Error, message));
                }
                throw new LSCommandExecutorException(message);
            }

            // Check for tests folder, if not exists create a new folder
            Path filePath = Paths.get(URI.create(docUri));
            ImmutablePair<Path, Path> testDirs = createTestFolderIfNotExists(filePath);
            File testsDir = testDirs.getRight().toFile();

            // Generate a unique name for the tests file
            File testFile = testsDir.toPath().resolve(generateTestFileName(filePath)).toFile();

            // Generate test content edits
            String pkgRelativeSourceFilePath = testDirs.getLeft().relativize(filePath).toString();
            Pair<BLangNode, Object> bLangNodePair = getBLangNode(line, column, docUri, docManager, lsCompiler,
                    context);

            Position position = new Position(0, 0);
            Range focus = new Range(position, position);
            BiConsumer<Integer, Integer> focusLineAcceptor = (focusLine, incrementer) -> {
                if (focusLine != null) {
                    position.setLine(focusLine);
                }
                position.setLine(position.getLine() + incrementer);
            };
            List<TextEdit> content = TestGenerator.generate(docManager, bLangNodePair, focusLineAcceptor,
                    builtSourceFile, pkgRelativeSourceFilePath, testFile);

            // Send edits
            List<Either<TextDocumentEdit, ResourceOperation>> edits = new ArrayList<>();
            VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier();
            identifier.setUri(testFile.toPath().toUri().toString());
            edits.add(Either.forRight(new CreateFile(testFile.toPath().toUri().toString())));

            TextDocumentEdit textEdit = new TextDocumentEdit(identifier, content);
            edits.add(Either.forLeft(textEdit));

            WorkspaceEdit workspaceEdit = new WorkspaceEdit(edits);
            ApplyWorkspaceEditParams editParams = new ApplyWorkspaceEditParams(workspaceEdit);
            if (client != null) {
                client.applyEdit(editParams);
                String message = "Tests generated into the file:" + testFile.toString();
                client.showMessage(new MessageParams(MessageType.Info, message));
                if (workspace.getExperimentalClientCapabilities().get(SHOW_TEXT_DOCUMENT.getValue())) {
                    Location location = new Location(identifier.getUri(), focus);
                    client.showTextDocument(location);
                }
            }
            return editParams;
        } catch (TestGeneratorException e) {
            String message = "Test generation failed!: " + e.getMessage();
            if (client != null) {
                client.showMessage(new MessageParams(MessageType.Error, message));
            }
            throw new LSCommandExecutorException(message, e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getCommand() {
        return COMMAND;
    }
}