root/lib/libedit/TEST/test_filecompletion.c
/*      $NetBSD: test_filecompletion.c,v 1.5 2019/09/08 05:50:58 abhinav Exp $  */

/*-
 * Copyright (c) 2017 Abhinav Upadhyay <abhinav@NetBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#include <assert.h>
#include <err.h>
#include <stdio.h>
#include <histedit.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include "filecomplete.h"
#include "el.h"

typedef struct {
        const wchar_t *user_typed_text; /* The actual text typed by the user on the terminal */
        const char *completion_function_input ; /*the text received by fn_filename_completion_function */
        const char *expanded_text[2]; /* the value to which completion_function_input should be expanded */
        const wchar_t *escaped_output; /* expected escaped value of expanded_text */
} test_input;

static test_input inputs[] = {
        {
                /* simple test for escaping angular brackets */
                L"ls ang",
                "ang",
                {"ang<ular>test", NULL},
                L"ls ang\\<ular\\>test "
        },
        {
                /* test angular bracket inside double quotes: ls "dq_ang */
                L"ls \"dq_ang",
                "dq_ang",
                {"dq_ang<ular>test", NULL},
                L"ls \"dq_ang<ular>test\""
        },
        {
                /* test angular bracket inside singlq quotes: ls "sq_ang */
                L"ls 'sq_ang",
                "sq_ang",
                {"sq_ang<ular>test", NULL},
                L"ls 'sq_ang<ular>test'"
        },
        {
                /* simple test for backslash */
                L"ls back",
                "back",
                {"backslash\\test", NULL},
                L"ls backslash\\\\test "
        },
        {
                /* backslash inside single quotes */
                L"ls 'sback",
                "sback",
                {"sbackslash\\test", NULL},
                L"ls 'sbackslash\\test'"
        },
        {
                /* backslash inside double quotes */
                L"ls \"dback",
                "dback",
                {"dbackslash\\test", NULL},
                L"ls \"dbackslash\\\\test\""
        },
        {
                /* test braces */
                L"ls br",
                "br",
                {"braces{test}", NULL},
                L"ls braces\\{test\\} "
        },
        {
                /* test braces inside single quotes */
                L"ls 'sbr",
                "sbr",
                {"sbraces{test}", NULL},
                L"ls 'sbraces{test}'"
        },
        {
                /* test braces inside double quotes */
                L"ls \"dbr",
                "dbr",
                {"dbraces{test}", NULL},
                L"ls \"dbraces{test}\""
        },
        {
                /* test dollar */
                L"ls doll",
                "doll",
                {"doll$artest", NULL},
                L"ls doll\\$artest "
        },
        {
                /* test dollar inside single quotes */
                L"ls 'sdoll",
                "sdoll",
                {"sdoll$artest", NULL},
                L"ls 'sdoll$artest'"
        },
        {
                /* test dollar inside double quotes */
                L"ls \"ddoll",
                "ddoll",
                {"ddoll$artest", NULL},
                L"ls \"ddoll\\$artest\""
        },
        {
                /* test equals */
                L"ls eq",
                "eq",
                {"equals==test", NULL},
                L"ls equals\\=\\=test "
        },
        {
                /* test equals inside sinqle quotes */
                L"ls 'seq",
                "seq",
                {"sequals==test", NULL},
                L"ls 'sequals==test'"
        },
        {
                /* test equals inside double quotes */
                L"ls \"deq",
                "deq",
                {"dequals==test", NULL},
                L"ls \"dequals==test\""
        },
        {
                /* test \n */
                L"ls new",
                "new",
                {"new\\nline", NULL},
                L"ls new\\\\nline "
        },
        {
                /* test \n inside single quotes */
                L"ls 'snew",
                "snew",
                {"snew\nline", NULL},
                L"ls 'snew\nline'"
        },
        {
                /* test \n inside double quotes */
                L"ls \"dnew",
                "dnew",
                {"dnew\nline", NULL},
                L"ls \"dnew\nline\""
        },
        {
                /* test single space */
                L"ls spac",
                "spac",
                {"space test", NULL},
                L"ls space\\ test "
        },
        {
                /* test single space inside singlq quotes */
                L"ls 's_spac",
                "s_spac",
                {"s_space test", NULL},
                L"ls 's_space test'"
        },
        {
                /* test single space inside double quotes */
                L"ls \"d_spac",
                "d_spac",
                {"d_space test", NULL},
                L"ls \"d_space test\""
        },
        {
                /* test multiple spaces */
                L"ls multi",
                "multi",
                {"multi space  test", NULL},
                L"ls multi\\ space\\ \\ test "
        },
        {
                /* test multiple spaces inside single quotes */
                L"ls 's_multi",
                "s_multi",
                {"s_multi space  test", NULL},
                L"ls 's_multi space  test'"
        },
        {
                /* test multiple spaces inside double quotes */
                L"ls \"d_multi",
                "d_multi",
                {"d_multi space  test", NULL},
                L"ls \"d_multi space  test\""
        },
        {
                /* test double quotes */
                L"ls doub",
                "doub",
                {"doub\"quotes", NULL},
                L"ls doub\\\"quotes "
        },
        {
                /* test double quotes inside single quotes */
                L"ls 's_doub",
                "s_doub",
                {"s_doub\"quotes", NULL},
                L"ls 's_doub\"quotes'"
        },
        {
                /* test double quotes inside double quotes */
                L"ls \"d_doub",
                "d_doub",
                {"d_doub\"quotes", NULL},
                L"ls \"d_doub\\\"quotes\""
        },
        {
                /* test multiple double quotes */
                L"ls mud",
                "mud",
                {"mud\"qu\"otes\"", NULL},
                L"ls mud\\\"qu\\\"otes\\\" "
        },
        {
                /* test multiple double quotes inside single quotes */
                L"ls 'smud",
                "smud",
                {"smud\"qu\"otes\"", NULL},
                L"ls 'smud\"qu\"otes\"'"
        },
        {
                /* test multiple double quotes inside double quotes */
                L"ls \"dmud",
                "dmud",
                {"dmud\"qu\"otes\"", NULL},
                L"ls \"dmud\\\"qu\\\"otes\\\"\""
        },
        {
                /* test one single quote */
                L"ls sing",
                "sing",
                {"single'quote", NULL},
                L"ls single\\'quote "
        },
        {
                /* test one single quote inside single quote */
                L"ls 'ssing",
                "ssing",
                {"ssingle'quote", NULL},
                L"ls 'ssingle'\\''quote'"
        },
        {
                /* test one single quote inside double quote */
                L"ls \"dsing",
                "dsing",
                {"dsingle'quote", NULL},
                L"ls \"dsingle'quote\""
        },
        {
                /* test multiple single quotes */
                L"ls mu_sing",
                "mu_sing",
                {"mu_single''quotes''", NULL},
                L"ls mu_single\\'\\'quotes\\'\\' "
        },
        {
                /* test multiple single quotes inside single quote */
                L"ls 'smu_sing",
                "smu_sing",
                {"smu_single''quotes''", NULL},
                L"ls 'smu_single'\\'''\\''quotes'\\\'''\\'''"
        },
        {
                /* test multiple single quotes inside double quote */
                L"ls \"dmu_sing",
                "dmu_sing",
                {"dmu_single''quotes''", NULL},
                L"ls \"dmu_single''quotes''\""
        },
        {
                /* test parenthesis */
                L"ls paren",
                "paren",
                {"paren(test)", NULL},
                L"ls paren\\(test\\) "
        },
        {
                /* test parenthesis inside single quote */
                L"ls 'sparen",
                "sparen",
                {"sparen(test)", NULL},
                L"ls 'sparen(test)'"
        },
        {
                /* test parenthesis inside double quote */
                L"ls \"dparen",
                "dparen",
                {"dparen(test)", NULL},
                L"ls \"dparen(test)\""
        },
        {
                /* test pipe */
                L"ls pip",
                "pip",
                {"pipe|test", NULL},
                L"ls pipe\\|test "
        },
        {
                /* test pipe inside single quote */
                L"ls 'spip",
                "spip",
                {"spipe|test", NULL},
                L"ls 'spipe|test'",
        },
        {
                /* test pipe inside double quote */
                L"ls \"dpip",
                "dpip",
                {"dpipe|test", NULL},
                L"ls \"dpipe|test\""
        },
        {
                /* test tab */
                L"ls ta",
                "ta",
                {"tab\ttest", NULL},
                L"ls tab\\\ttest "
        },
        {
                /* test tab inside single quote */
                L"ls 'sta",
                "sta",
                {"stab\ttest", NULL},
                L"ls 'stab\ttest'"
        },
        {
                /* test tab inside double quote */
                L"ls \"dta",
                "dta",
                {"dtab\ttest", NULL},
                L"ls \"dtab\ttest\""
        },
        {
                /* test back tick */
                L"ls tic",
                "tic",
                {"tick`test`", NULL},
                L"ls tick\\`test\\` "
        },
        {
                /* test back tick inside single quote */
                L"ls 'stic",
                "stic",
                {"stick`test`", NULL},
                L"ls 'stick`test`'"
        },
        {
                /* test back tick inside double quote */
                L"ls \"dtic",
                "dtic",
                {"dtick`test`", NULL},
                L"ls \"dtick\\`test\\`\""
        },
        {
                /* test for @ */
                L"ls at",
                "at",
                {"atthe@rate", NULL},
                L"ls atthe\\@rate "
        },
        {
                /* test for @ inside single quote */
                L"ls 'sat",
                "sat",
                {"satthe@rate", NULL},
                L"ls 'satthe@rate'"
        },
        {
                /* test for @ inside double quote */
                L"ls \"dat",
                "dat",
                {"datthe@rate", NULL},
                L"ls \"datthe@rate\""
        },
        {
                /* test ; */
                L"ls semi",
                "semi",
                {"semi;colon;test", NULL},
                L"ls semi\\;colon\\;test "
        },
        {
                /* test ; inside single quote */
                L"ls 'ssemi",
                "ssemi",
                {"ssemi;colon;test", NULL},
                L"ls 'ssemi;colon;test'"
        },
        {
                /* test ; inside double quote */
                L"ls \"dsemi",
                "dsemi",
                {"dsemi;colon;test", NULL},
                L"ls \"dsemi;colon;test\""
        },
        {
                /* test & */
                L"ls amp",
                "amp",
                {"ampers&and", NULL},
                L"ls ampers\\&and "
        },
        {
                /* test & inside single quote */
                L"ls 'samp",
                "samp",
                {"sampers&and", NULL},
                L"ls 'sampers&and'"
        },
        {
                /* test & inside double quote */
                L"ls \"damp",
                "damp",
                {"dampers&and", NULL},
                L"ls \"dampers&and\""
        },
        {
                /* test completion when cursor at \ */
                L"ls foo\\",
                "foo",
                {"foo bar", NULL},
                L"ls foo\\ bar "
        },
        {
                /* test completion when cursor at single quote */
                L"ls foo'",
                "foo'",
                {"foo bar", NULL},
                L"ls foo\\ bar "
        },
        {
                /* test completion when cursor at double quote */
                L"ls foo\"",
                "foo\"",
                {"foo bar", NULL},
                L"ls foo\\ bar "
        },
        {
                /* test multiple completion matches */
                L"ls fo",
                "fo",
                {"foo bar", "foo baz"},
                L"ls foo\\ ba"
        },
        {
                L"ls ba",
                "ba",
                {"bar <bar>", "bar <baz>"},
                L"ls bar\\ \\<ba"
        }
};

static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{(";

/*
 * Custom completion function passed to fn_complet, NULLe.
 * The function returns hardcoded completion matches
 * based on the test cases present in inputs[] (above)
 */
static char *
mycomplet_func(const char *text, int index)
{
        static int last_index = 0;
        size_t i = 0;
        if (last_index == 2) {
                last_index = 0;
                return NULL;
        }

        for (i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) {
                if (strcmp(text, inputs[i].completion_function_input) == 0) {
                        if (inputs[i].expanded_text[last_index] != NULL)
                                return strdup(inputs[i].expanded_text[last_index++]);
                        else {
                                last_index = 0;
                                return NULL;
                        }
                }
        }

        return NULL;
}

int
main(int argc, char **argv)
{
        EditLine *el = el_init(argv[0], stdin, stdout, stderr);
        size_t i; 
        size_t input_len;
        el_line_t line;
        wchar_t *buffer = malloc(64 * sizeof(*buffer));
        if (buffer == NULL)
                err(EXIT_FAILURE, "malloc failed");

        for (i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) {
                memset(buffer, 0, 64 * sizeof(*buffer));
                input_len = wcslen(inputs[i].user_typed_text);
                wmemcpy(buffer, inputs[i].user_typed_text, input_len);
                buffer[input_len] = 0;
                line.buffer = buffer;
                line.cursor = line.buffer + input_len ;
                line.lastchar = line.cursor - 1;
                line.limit = line.buffer + 64 * sizeof(*buffer);
                el->el_line = line;
                fn_complete(el, mycomplet_func, NULL, break_chars, NULL, NULL, 10, NULL, NULL, NULL, NULL);

                /*
                 * fn_complete would have expanded and escaped the input in el->el_line.buffer.
                 * We need to assert that it matches with the expected value in our test data
                 */
                printf("User input: %ls\t Expected output: %ls\t Generated output: %ls\n",
                                inputs[i].user_typed_text, inputs[i].escaped_output, el->el_line.buffer);
                assert(wcscmp(el->el_line.buffer, inputs[i].escaped_output) == 0);
        }
        el_end(el);
        return 0;

}