Общие сведения

ВступлениеПодготовка к запускуАрхитектура платформы TestoПорядок запускаПолитика запуска тестов

Обучающие материалы по Testo для Hyper-V

Часть 1. Самый первый тестЧасть 2. Устанавливаем Ubuntu ServerЧасть 3. Доступ в Интернет из виртуальной машиныЧасть 4. Гостевые дополненияЧасть 5. ПараметрыЧасть 6. КешированиеЧасть 7. Связываем две машины по сетиЧасть 8. ФлешкиЧасть 9. МакросыЧасть 10. Конструкция ifЧасть 11. No snapshotsЧасть 12. Управление мышкойЧасть 13. Импортирование жёстких дисковЧасть 14. JS-селекторыЧасть 15. Циклы

Обучающие материалы по Testo для QEMU

Часть 1. Самый первый тестЧасть 2. Устанавливаем Ubuntu ServerЧасть 3. Гостевые дополненияЧасть 4. ПараметрыЧасть 5. КешированиеЧасть 6. Доступ в Интернет из виртуальной машиныЧасть 7. Связываем две машины по сетиЧасть 8. ФлешкиЧасть 9. МакросыЧасть 10. Конструкция ifЧасть 11. No snapshotsЧасть 12. Управление мышкойЧасть 13. Импортирование жёстких дисковЧасть 14. JS-селекторыЧасть 15. ЦиклыЧасть 16. Макросы с объявлениями

Спецификация языка

Общая структура скриптовых файловБазовые конструкции языкаOбъявление виртуальной машиныОбъявление виртуального флеш-накопителяОбъявление виртуальной сетиПараметрыОбъявление тестовМакросыДействия с виртуальными машинамиДействия с мышкойПоиск изображений на экранеДействия с виртуальными флеш-накопителямиУсловияЦиклыСписок идентификаторов клавиш

Запросы на языке Javascript

Общая концепция построения JS-селекторовВстроенные глобальные функции JavascriptИсключенияКласс TextTensorКласс ImgTensorКласс Point

Макросы

Макросы позволяют объединять часто используемые участки кода в виде обособленных именованных блоков. Cуществует три вида макросов в языке Testo-lang: макросы с действиями, макросы с командами и макросы с объявлениями. Все типы макросов имеют одинаковый формат заголовка и отличаются только "наполнением".

Общий формат объявления макроса выглядит следующим образом:

macro <name> ([arg1, arg2, ... argn="default_value1", argn+1="default_value2" ...]) {
    <macro_body>
}

Макросы должны иметь имя в формате идентификатора, уникальное среди макросов. Макросы могут принимать агументы, к которым затем можно получать доступ внутри тела макроса. На текущий момент можно передавать только строковые аргументы. Аргументы могут иметь значение по-умолчанию. Внутри значения по-умолчанию можно обращаться к аргументам макроса, как это показано здесь.

Макросы с действиями

Макрос с действиями - это макрос, тело которого состоит из действий. Объявление такого макроса выглядит так:

macro <name> ([arg1, arg2, ... argn="default_value1", argn+1="default_value2" ...]) {
    action1
    action2
    ...
}

В теле макроса можно использовать все те конструкции, которые можно указывать в теле команд, из которых состоит тест: действия, условия, циклы, вызов других макросов.

Пример

# Макрос login дожидается появления на экране приглашения к вводу логина
# и предпринимает попытку войти в систему. Принимает два аргумента:
# login и password, который вычисляется на основе значения параметра default_password.
# default_password должен быть определён.
macro login(login, password="${default_password}") {
    wait "login:"; type "${login}"; press Enter
    wait "Password:"; type "${password}"; press Enter
    wait "Welcome to Ubuntu"
}

# Макрос unplug_nic выключает виртуальную машину
# и отсоединяет указанный в nic_name сетевой адаптер
macro unplug_nic(nic_name) {
    shutdown
    unplug nic "${nic_name}"
}

test my_test {
    my_ubuntu {
        ...
        unplug_nic("internet")
        start

        #мы передаем только один аргумент, потому что пользователь
        # root имеет пароль default_password
        login("root")
        ...
    }
}

Макросы с командами

Макрос с командами - это макрос, тело которого состоит из команд. Формат команд можно посмотреть здесь. Объявление такого макроса выглядит следующим образом:

macro <name> ([arg1, arg2, ... argn="default_value1", argn+1="default_value2" ...]) {
    command1
    command2
    ...
}

Напомню, что команда состоит из двух частей - имя виртуальной сущности (флешки или машины) и действия (набора действий), которое нужно применить к этой сущности. Имя виртуальной сущности может быть представлено как в виде идентификатора (client), так и в виде строки ("client").

В макросах с командами адресация сущностей с помощью строк может оказаться особенно полезной, потому что в таком случае внутри строк можно обращаться к аргументам макроса. Получается, что таким образом в макросы можно передавать имена виртуальных сущностей. В качестве примера давайте рассмотрим следующий макрос:

macro copy_folder(first_machine, second_machine, flash_to_copy, src, dst) {
    "${first_machine}" {
        plug flash "${flash_to_copy}"
        exec bash """
            cp -r ${src} /media/flash/${src}
        """
        unplug_flash "${flash_to_copy}"
    }

    "${second_machine}" {
        plug flash "${flash_to_copy}"
        exec bash """
            cp -r /media/flash/${src} "${dst}"
        """
        unplug_flash "${flash_to_copy}"
    }
}

Задача этого макроса - скопировать папку с одной виртуальной машины на другую с помощью виртуальной флешки. Также подразумевается, что на машинах установлены гостевые дополнения (они необходимы, чтобы сработало действие exec bash). В качестве аргументов макрос принимает имена виртуальных машин, имя флешки, а также путь-источник и путь назначения.

Обратите внимание, что макрос состоит из команд, но при этом имена сущностей параметризированы, то есть этот макрос можно применять к любым виртуальным машинам.

Ниже можно посмотреть пример вызова этого макроса.

Макрос может либо состоять целиком из команд, либо целиком из действий. Не допускается существование макросов, состоящих частично из действий и частично из команд.

Макросы с объявлениями

Макрос с объявлениями - это макрос, тело которого состоит из объявлений тестов, а также виртуальных машин, флешек и сетей. Объявление такого макроса выглядит следующим образом:

macro <name> ([arg1, arg2, ... argn="default_value1", argn+1="default_value2" ...]) {
    declaration_1
    declaration_2
    ...
}

Где declaration_i может быть одной из следующих сущностей:

  • Объявление теста;
  • Объявление виртуальной машины;
  • Объявление виртуальной флешки;
  • Объявление виртуальной сети;
  • Вызов другого макроса с объявлениями.

Внутри макросов запрещено объявлять параметры и другие макросы. Также запрещено использовать директиву include.

Макросы с объявлениями позволяют объединять однотипные тесты и даже однотипные стенды в единые конструкции. Такие макросы могут особенно пригодиться, если требуется сгенерировать несколько одинаковых (или практически одинаковых) тестов.

Примеры:

Давайте представим ситуацию, что вам необходимо создать набор тестов (установить ОС + подготовить ОС) для одной и той же ОС, но с разной разрядностью (32 и 64 бита). В таком случае вам потребуется две виртуальные машины (одна для разрядности 32 бита, другая для разрядности 64 бита) и 4 теста (установить ОС 32 бита, подготовить ОС 32 бита, установить ОС 64 бита, подготовить ОС 64 бита). Допустим вы также хотите выделить больше оперативной памяти для ОС с разрядностью 64 бита. Для этой задачи отлично подойдёт макрос с объявлениями:

macro generate_tests(bits, memory) {

    machine "vm_${bits}" {
        cpus: 2
        ram: "${memory}"
        iso: "${ISO_DIR}/os_${bits}.iso"
        disk main: {
            size: 5Gb
        }
    }

    test "vm_${bits}_install_os" {
        "vm_${bits}" {
            install_os("${bits}")
        }
    }

    test "vm_${bits}_prepare_os": "vm_${bits}_install_os" {
        "vm_${bits}_prepare_os" prepare()
    }

}

Этот макрос позволяет параметризировать как сам стенд (пусть он состоит всего из одной виртуальной машины), так и тесты с участием этого стенда. Для виртуальной машины параметризируется её имя, количество оперативной памяти, а также путь к установочному iso-образу (мы предполагаем, что для ОС разных разрядностей существуют разные образы). В тестах параметризируется имя тестов (и родителей), а также имя виртуальных машин и действий с ними. В тесте "vm_${bits}_install_os" происходит установка ОС нужной разрядности (мы предполагаем, что порядок установки практически одинаковый и может быть инкапсулирован в макрос с действиями install_os()). В тесте "vm_${bits}_prepare_os" и вовсе вызывается один и тот же макрос с действиями prepare(), который выполняет идентичные действия по настройки для обоих виртуальных машин.

При этом никто не мешает разбить этот макрос на два макроса: в одном макросе можно объявить виртуальную машину, а в другом - тесты:

macro generate_vms(bits, memory) {

    machine "vm_${bits}" {
        cpus: 2
        ram: "${memory}"
        iso: "${ISO_DIR}/os_${bits}.iso"
        disk main: {
            size: 5Gb
        }
    }
}

macro generate_tests(bits) {

    test "vm_${bits}_install_os" {
        "vm_${bits}" {
            install_os("${bits}")
        }
    }

    test "vm_${bits}_prepare_os": "vm_${bits}_install_os" {
        "vm_${bits}_prepare_os" prepare()
    }

}

Заметьте, что объявление макроса с машиной и с тестами не означает реальное объявление каких-либо машин и тестов. Чтобы это произошло, макрос необходимо вызывать (см. далее).

Вызов макросов

Вызов макросов может являться либо действием, либо командой, либо объявлением - всё зависит от содержимого макроса. Если макрос содержит команды, то его необходимо вызывать как команду (т.е. как элемент теста). Если же макрос содержит действия, то, как и любое действие, вызов такого макроса должен происходить внутри команд. Если же макрос содержит объявления, то его необходимо вызывать либо на самом верхнем уровне - на одном уровне с объявлениями виртуальных машин, флешек, сетей и тестов - либо внутри других макросов с объявлениями наряду с другими объявлениями (см. пример 5 ниже).

Попытка вызывать макрос в неправильном месте приведет к ошибке. Например, если вызвать макрос с объявлениями как действие - возникнет ошибка.

Вызов макроса имеет следующий формат:

<macro_name> ([param1, param2, ...])

Аргументы: Количество аргументов не должно быть больше, чем количество аргументов в объявлении макроса. Если при вызове макроса передается меньше аргументов, чем в его объявлении, то недостающие аргументы будут вычислены на основе значений по-умолчанию (если таковые имеются). Допускаются только строковые аргументы.

Пример 1

Пример ниже демонстрирует сложную цепочку вызовов макросов с действиями. Обратите внимание, что макрос должен содержать только действия, применимые к той сущности, для которой этот макрос вызывается. То же самое касается вложенных вызовов макросов.

macro suits_vm() {
    press Right, Enter

    if (check "Hello") {
        type "World"
    }

    suits_fd_and_vm_with_ga()
}

# Может подойти флешке (в любой ситуации) и
# виртуальной машине, если на ней установлены
# гостевые дополнения
macro suits_fd_and_vm_with_ga() {
    copyto "/some_file_on_host" "/some_file_on_guest"
}

macro suits_both() {
    print "Hello world"

    if (NOT DEFINED some_var) {
        abort "some var is not defined"
    }
}

machine some_vm {
    ...
}

flash some_flash {
    ...
}

test macro_call_usage_example {
    # Будет верным только если на виртуалке some_vm установлены гостевые дополнения
    some_vm suits_vm()

    # Будет корректным в любом случае, для флешек всегда доступно действие copyto
    some_flash_drive suits_fd_and_vm_with_ga()

    # Верно всегда
    some_vm suits_both()
    some_flash_drive suits_both()

    # Ошибка: для флешек нельзя делать проверку check
    some_flash_drive suits_vm()
}

Пример 2

В этом примере продемонстрирован вызов макроса с командами copy_folder, объявление которого можно посмотреть выше.

machine client {
    ...
}

machine server {
    ...
}

machine gateway {
    ...
}

machine firewall {
    ...
}

flash copy_flash {
    ...
}

test client_server_test {
    copy_folder("client", "server", "copy_flash", "/opt", "/opt")
}

test firewall_gateway_test {
    copy_folder("firewall", "gateway", "copy_flash", "/opt", "/opt")
}

test some_test {
    client {
        copy_folder() #Ошибка - этот макрос содержит команды, а мы пытаемся его вызывать как действие
    }
}

Пример 3

Разберём пример с макросами с объявлениями. Как уже упоминалось, объявление макроса не означает объявление самих сущностей внутри макроса. Для того, чтобы объявить сущности внутри макроса - этот макрос необходимо вызывать:

macro generate_tests(bits, memory) {

    machine "vm_${bits}" {
        cpus: 2
        ram: "${memory}"
        iso: "${ISO_DIR}/os_${bits}.iso"
        disk main: {
            size: 5Gb
        }
    }

    test "vm_${bits}_install_os" {
        "vm_${bits}" {
            install_os("${bits}")
        }
    }

    test "vm_${bits}_prepare_os": "vm_${bits}_install_os" {
        "vm_${bits}_prepare_os" prepare()
    }

}

generate_tests("32", "4Gb")
generate_tests("64", "8Gb")

# Теперь можно обращаться к тестам, которые были сгенерированы макросом

test some_test_for_32_bit_os: vm_32_prepare_os {
    vm_32 {
        print "Hello world"
    }
}

Пример 4

Как уже упоминалось выше, можно объявлять машины и тесты в разных макросах:

macro generate_vms(bits, memory) {

    machine "vm_${bits}" {
        cpus: 2
        ram: "${memory}"
        iso: "${ISO_DIR}/os_${bits}.iso"
        disk main: {
            size: 5Gb
        }
    }
}

macro generate_tests(bits) {

    test "vm_${bits}_install_os" {
        "vm_${bits}" {
            install_os("${bits}")
        }
    }

    test "vm_${bits}_prepare_os": "vm_${bits}_install_os" {
        "vm_${bits}_prepare_os" prepare()
    }

}

# Ошибка! генерация тестов ДО генерации виртуальных машин приведет
# к тому, что в тесте будет использоваться неизвестная виртуальная машина
generate_tests("32")


# Теперь - правильно
generate_vms("32", "4Gb")
generate_tests("32")

Пример 5

Внутри макросов с объявлениями можно вызывать другие макросы с объявлениями:

macro generate_vms(bits, memory) {

    machine "vm_${bits}" {
        cpus: 2
        ram: "${memory}"
        iso: "${ISO_DIR}/os_${bits}.iso"
        disk main: {
            size: 5Gb
        }
    }
}

macro generate_tests(bits, memory) {
    generate_vms("${bits}", "${memory}")

    test "vm_${bits}_install_os" {
        "vm_${bits}" {
            install_os("${bits}")
        }
    }

    test "vm_${bits}_prepare_os": "vm_${bits}_install_os" {
        "vm_${bits}_prepare_os" prepare()
    }
}

# Теперь такой вызов - не проблема
# Потому что генерация тестов влечет за собой
# Генерацию нужной виртуальной машины
generate_tests("32", "4Gb")