Golang покрытие кода для повторного выполнения процесса? - PullRequest
2 голосов
/ 28 февраля 2020

Для обнаружения Linux пространств имен при определенных условиях мой пакет с открытым исходным кодом Golang lxkns должен повторно выполнить приложение, в котором он используется как новый дочерний процесс, чтобы иметь возможность переключите пространства имен монтирования до того, как время выполнения Golang увеличится. Работа Linux монтирования пространств имен делает невозможным переключение их из Golang приложений после того, как среда выполнения ускорила потоки ОС.

Это означает, что исходный процесс "P" перезапускается. - запускает копию себя как дочерний «C» ( reexe c пакет), передавая специальную индикацию через дочернюю среду, которая сигнализирует дочернему элементу только для запуска указанного c " функция action, принадлежащая включенному пакету «lxkns» (подробности см. ниже), вместо того, чтобы нормально запускать все приложение (избегая бесконечных рекурсивно порождаемых потомков).

forkchild := exec.Command("/proc/self/exe")
forkchild.Start()
...
forkchild.Wait()

В данный момент я вызываю покрытие тестирует из кода VisualStudio, который запускает:

go test -timeout 30s -coverprofile=/tmp/vscode-goXXXXX/go-code-cover github.com/thediveo/lxkns

Итак, «P» повторно выполняет копию «C» самого себя и говорит ему выполнить какое-то действие «A», вывести некоторый результат в стандартный вывод, а затем немедленно прекратить. «P» ожидает вывода «C», анализирует его и затем продолжает выполнение программы.

В модульном тесте используется Ginkgo / Gomega и выделенный TestMain, чтобы поймать, когда Тест перезапускается как дочерний, чтобы запустить только запрошенную функцию «действие».

package lxkns

import (
    "os"
    "testing"

    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    "github.com/thediveo/gons/reexec"
)

func TestMain(m *testing.M) {
    // Ensure that the registered handler is run in the re-executed child. This
    // won't trigger the handler while we're in the parent, because the
    // parent's Arg[0] won't match the name of our handler.
    reexec.CheckAction()
    os.Exit(m.Run())
}

func TestLinuxKernelNamespaces(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "lxkns package")
}

Я также хотел бы создать данные покрытия кода из повторно выполненного дочернего процесса.

  • Можно ли включить покрытие кода из самой тестируемой программы и как это сделать?
  • Можно ли затем добавить данные покрытия кода, записанные ребенком, в данные покрытия покрытия родительский процесс "P"?
  • Записывает ли среда выполнения Golang только данные покрытия при выходе и перезаписывает ли указанный файл или добавляет? (Я уже был бы рад за указатель на соответствующие источники времени выполнения.)

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

1 Ответ

0 голосов
/ 08 марта 2020

После того, как @ Volker прокомментировал мой вопрос, я понял, что должен принять вызов, и сразу же пошел к исходному коду Go testing. Хотя предложение @ marco.m полезно во многих случаях, оно не может справиться с моим, по общему мнению, странным сценарием использования. Механики testing, относящиеся к моему первоначальному вопросу, сильно упрощены:

  • cover. go: реализует coverReport(), который записывает файл данных покрытия ( в текстовом формате ASCII); если файл уже существует (устаревшая версия из предыдущего запуска), то он будет сначала обрезан. Обратите внимание, что coverReport() имеет раздражающую привычку выводить некоторую «статистическую» информацию в os.Stdout.
  • testing. go:
    • получает аргументы CLI -test.coverprofile= и -test.outputdir= из os.Args (через пакет флагов). Если также реализован toOutputDir(path), какие файлы профиля обложки помещаются в -test.outputdir, если он указан.
    • Но когда вызывается coverReport()? Проще говоря, в конце testing.M.Run().

Теперь, с этими знаниями под поясом, начинают появляться сумасшедшие решения, вроде "Go -ing Bad ";)

  • Обтекание testing.M в специальной версии с включенным повторным выполнением reexec.testing.M: определяет, работает ли оно с включенным покрытием:
    • если это «родительский» процесс P, то он запускает тесты в обычном режиме, а затем собирает файлы данных профиля покрытия из повторно выполненных дочерних процессов C и объединяет их в файл данных профиля покрытия P.
    • в то время как в P и когда только собирается повторно выполнить новый дочерний элемент C, для дочернего элемента C назначается новое имя файла данных выделенного профиля покрытия. C затем получает имя файла через его "personal" -test.coverprofile= CLI arg.
    • , когда в C мы запускаем требуемую функцию действия. Затем нам нужно запустить пустой набор тестов, чтобы инициировать запись данных профиля покрытия для C. Для этого функция повторного выполнения в P добавляет test.run= с очень специальным « Bielefeld test pattern», который, скорее всего, приведет к пустому результату. Помните, что P - после выполнения всех своих тестов - подберет отдельные C файлы данных профиля покрытия и объединит их с P.
  • , когда профилирование покрытия не включается, то никаких специальных действий предпринимать не нужно.

Недостатком этого решения является то, что оно зависит от негарантированного поведения Go testing относительно того, как и когда он пишет отчеты о покрытии кода. Но так как пакет обнаружения пространства имен Linux ядра уже продвигает Go, вероятно, даже сильнее, чем Docker libnetwork, это всего лишь квант дальше по краю.

Для разработчика теста вся энчилада скрыт внутри «улучшенной» оболочки rxtst.M.

import (
    "testing"
    rxtst "github.com/thediveo/gons/reexec/testing"
)

func TestMain(m *testing.M) {
    // Ensure that the registered handler is run in the re-executed child.
    // This won't trigger the handler while we're in the parent. We're using
    // gons' very special coverage profiling support for re-execution.
    mm := &rxtst.M{M: m}
    os.Exit(mm.Run())
}

Запуск всего lxkns набора тестов с покрытием, предпочтительно с использованием go-acc (go точное покрытие кода расчет), затем показывает на скриншоте ниже, что функция discoverNsfsBindMounts() была запущена один раз (1). Эта функция напрямую не вызывается откуда-либо в P. Вместо этого эта функция регистрируется и затем запускается в повторно выполненном дочернем элементе C. Ранее не сообщалось о покрытии кода для discoverNsfsBindMounts(), но теперь с помощью пакета github.com / thediveo / gons / reexec / testing * покрытие кода 1068 * для C прозрачно объединяется с покрытием кода P.

enter image description here

...