Clique aqui para saber mais sobre o Dropbox Transfer e inscreva-se na lista de espera para ser adicionado ao programa beta.

O software do lado do servidor do Dropbox vive em um grande monorepo. Uma lição que aprendemos sobre o dimensionamento do monorepo é minimizar o número de operações globais que operam no repositório como um todo. Anos atrás, era razoável executar todo as nossas opções de teste em todas as confirmações no repositório. Esse esquema tornou-se insustentável à medida que adicionamos mais testes. Uma ineficiência óbvia é a execução inútil de testes que não podem ser afetados por uma alteração específica.

Resolvemos esse problema com a ajuda do nosso sistema de compilação. O código do nosso monorepo é construído e testado exclusivamente com o Bazel. Abstratamente, o Bazel vê o repositório como um conjunto de destinos (arquivos, binários, bibliotecas, testes, etc.) e as dependências entre eles. Em particular, Bazel conhece o gráfico de dependência entre todos os arquivos de origem e testes no repositório. Modificamos nosso sistema de integração contínua para extrair essas informações de dependência do Bazel e usá-las para calcular o conjunto de testes afetados por uma confirmação específica. Isso nos permite reduzir bastante o número de testes executados na maioria das confirmações enquanto ainda estamos corretos.

Como um teste específico não é mais executado em todas as confirmações, usamos o histórico anterior para determinar seu status nas confirmações nas quais não foi executado. Se um teste é executado na confirmação, N mas não é afetado pela confirmação N+1, podemos considerar que o teste tem o mesmo status nas duas confirmações. Dessa maneira, propagamos um status para cada teste no repositório para cada confirmação em que ele existe.

Para economizar recursos extras, não executamos todos os testes afetados em todas as confirmações. Acumulamos o conjunto de testes afetados para as confirmações em um período fixo, calculando a união do conjunto de testes afetados em cada confirmação no período. Em seguida, executamos o conjunto acumulado de testes afetados para esse período na última confirmação no período. O tempo entre as compilações de rollup é um uso sintonizável do recurso de negociação de parâmetros em relação à pontualidade dos resultados do teste. Esse lote pode dificultar a detecção de quebras porque o primeiro comprometimento em observar uma falha no teste pode não ser realmente culpado. No entanto, nosso sistema automatizado de detecção de quebras, Athena, é capaz de dividir os testes com falha ao longo de todo o período de rollup para encontrar as alterações quebradas precisas.

Nosso sistema de implantação de produção distribui software para hosts na forma de imagens SquashFS. Temos uma regra Bazel personalizada que cria imagens SquashFS para o sistema de implantação consumir.

Geralmente, exigimos que o software passe nos testes antes de ser enviado ao nosso ambiente de produção. Esse requisito é imposto pelo nosso sistema de implantação. Historicamente, permitimos enviar software de um commit específico apenas se todos os testes no repositório passassem nele, seja diretamente executado no commit ou através da propagação de commits anteriores. Embora simples, esse modelo não se adapta bem. É frustrante ter a implantação bloqueada porque um teste completamente não relacionado está falhando. Mesmo que todos os testes passem, o crescimento do repositório ao longo do tempo leva mais e mais tempo para provar que uma confirmação específica é completamente verde, apesar de executar apenas os testes afetados em cada confirmação. Para quebrar o antigo sistema “monolítico verde”, permitimos que todos os pacotes implantáveis ​​especifiquem “testes de liberação”, o conjunto de testes necessários para passar antes de executá-lo. Sintaxe do padrão de destino de Bazel.

Por exemplo, a implantação de um servidor de ping C ++ simples no Dropbox pode ter
um BUILDarquivo Bazel como este:

cc_library(
     name = "ping_lib",
     srcs = ["ping.cc"],
)

cc_binary(
    name = "ping_server",
    srcs = ["ping_server.cc"],
    deps = [":ping_lib"],
)

cc_test(
    name = "ping_test",
    srcs = ["ping_test.cc"],
    deps = [":ping_lib"],
)

dbx_pkg_sqfs(
    name = "ping_server.sqfs",
    data = [
        ":server",
    ],
    release_tests = [
        "//ping_server/...",
    ],
)

Este arquivo declara uma biblioteca C ++, binária e teste usando as regras padrão incorporadas do Bazel C ++ . O ping_server.sqfsdestino produz um SquashFS contendo o ping_serverbinário. ping_server.sqfsseria implementável em confirmações nas quais todos os testes //ping_server/e seus subpacotes haviam passado.

Como mencionado anteriormente, conservamos recursos agregando execuções de teste em várias construções de confirmação. Isso potencialmente introduz uma latência extra entre quando um commit chega e quando é implementável. Se um engenheiro fizer uma alteração no servidor ping e quiser implantá-lo
imediatamente, poderá solicitar que nosso sistema de integração contínua execute ping_server.sqfsos testes de liberação assim que a confirmação for concluída. Isso acontece independentemente de onde a confirmação cai no período de rollup.

Deixamos a decisão do que colocar em release_testsequipes individuais. É comum incluir testes de um pacote, bem como testes de bibliotecas de dependência críticas. Projetos mais conservadores podem incluir alguns dos testes de suas dependências reversas. Quando estávamos desenvolvendo o recurso de testes de versão, experimentamos gerar automaticamente o conjunto de testes inspecionando as dependências do código empacotado. No entanto, não foi possível desenvolver uma heurística intuitiva que incluísse simultaneamente os testes esperados pelas pessoas e reduzisse significativamente o número de testes necessários para liberar um pacote.

Interessado em criar ótimas ferramentas de desenvolvedor em escala? Estamos contratando!

Clique aqui para saber mais sobre o Dropbox Transfer e inscreva-se na lista de espera para ser adicionado ao programa beta.

Facebook
Pinterest
Twitter
LinkedIn

Deixe uma resposta