This commit is contained in:
Nicolas Jeannerod 2024-08-30 17:23:55 +02:00
parent 5fd5c37834
commit b9cf2d5e10
Signed by: Niols
GPG key ID: 35DB9EC8886E1CB8
5 changed files with 198 additions and 12 deletions

View file

@ -60,12 +60,12 @@ NOTE: it sometimes takes a while for the services to start up, and in the meanti
## NixOS Tests ## NixOS Tests
Tests live in the aptly named `tests/` directory, and can be accessed at the flake URI `.#checks.<system>.<test-name>` e.g. `nix build .#checks.x86_64-linux.mastodon-garage`. Tests live in the aptly named `tests/` directory, and can be accessed at the flake URI `.#checks.<system>.<test-name>` e.g. `nix build .#checks.x86_64-linux.mastodon-garage`.
They can also be run interactively with They can also be run interactively with
``` ```
nix build .#checks.<system>.<test>.driverInteractive nix build .#checks.<system>.<test>.driverInteractive
./result/bin/nixos-test-driver 2>output ./result/bin/nixos-test-driver 2>output
```` ````
you can `less -F output` from a different terminal to follow along. you can `less output` and then `F` from a different terminal to follow along.
These tests are also equiped with the same port forwarding as the VMs, so when running interactively you should be able to access services through a browser running on your machine. These tests are also equiped with the same port forwarding as the VMs, so when running interactively you should be able to access services through a browser running on your machine.
@ -105,5 +105,3 @@ When mastodon is running in production mode, we have a few problems:
- mastodon is trying to fetch `custom.css` from https://mastodon.localhost (no port), which is not the configured `LOCAL_DOMAIN`, so it's unclear why. - mastodon is trying to fetch `custom.css` from https://mastodon.localhost (no port), which is not the configured `LOCAL_DOMAIN`, so it's unclear why.
NixOS tests do not take the configuration from `virtualisation.vmVariant`. This seems like an oversight since people don't tend to mix normal NixOS configurations with the ones they're using for tests. This should be pretty easy to rectify upstream. NixOS tests do not take the configuration from `virtualisation.vmVariant`. This seems like an oversight since people don't tend to mix normal NixOS configurations with the ones they're using for tests. This should be pretty easy to rectify upstream.

View file

@ -2,14 +2,18 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1724846166, "lastModified": 1723726852,
"narHash": "sha256-Um1Ahz09XHepSA1QQmdQk8nbsJEwHe54gP3naWp6D94=", "narHash": "sha256-lRzlx4fPRtzA+dgz9Rh4WK5yAW3TsAXx335DQqxY2XY=",
"path": "/home/qolen/nixpkgs", "owner": "radvendii",
"type": "path" "repo": "nixpkgs",
"rev": "9286249a1673cf5b14a4793e22dd44b70cb69a0d",
"type": "github"
}, },
"original": { "original": {
"path": "/home/qolen/nixpkgs", "owner": "radvendii",
"type": "path" "ref": "nixos_rebuild_tests",
"repo": "nixpkgs",
"type": "github"
} }
}, },
"root": { "root": {

View file

@ -52,6 +52,7 @@
checks.${system} = { checks.${system} = {
mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; }; mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; };
pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; };
}; };
devShells.${system}.default = pkgs.mkShell { devShells.${system}.default = pkgs.mkShell {

View file

@ -26,7 +26,6 @@ let
WebDriverWait(driver, 90).until( WebDriverWait(driver, 90).until(
lambda x: x.find_element(By.CLASS_NAME, "status")) lambda x: x.find_element(By.CLASS_NAME, "status"))
# XXX: how do I save this to the derivation output?
driver.save_screenshot("/mastodon-screenshot.png") driver.save_screenshot("/mastodon-screenshot.png")
driver.close() driver.close()
@ -58,7 +57,7 @@ pkgs.nixosTest {
}; };
}; };
testScript = {nodes, ...}: '' testScript = ''
import re import re
import time import time

184
tests/pixelfed-garage.nix Normal file
View file

@ -0,0 +1,184 @@
{ pkgs, self }:
let
lib = pkgs.lib;
rebuildableTest = import ./rebuildableTest.nix pkgs;
seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
{
libraries = with pkgs.python3Packages; [ selenium ];
} ''
import sys
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.options import Options
email = sys.argv[1]
password = sys.argv[2]
green_path = "${./green.png}"
screenshot_path = "/screenshot.png"
# Create and configure driver. It is important to set the window size such that
# the “Create New Post” button is visible.
print("Create and configure driver...")
options = Options()
options.add_argument("--headless=new")
service = webdriver.ChromeService(executable_path="${lib.getExe pkgs.chromedriver}") # noqa: E501
driver = webdriver.Chrome(options=options, service=service)
driver.implicitly_wait(10)
driver.set_window_size(1920, 1200)
# FIXME: Replace the By.XPATH by By.CSS_SELECTOR.
# Go to Pixelfed and login.
print("Open login page...")
driver.get("http://pixelfed.localhost/login")
print("Enter email...")
driver.find_element(By.ID, "email").send_keys(email)
print("Enter password...")
driver.find_element(By.ID, "password").send_keys(password)
# FIXME: This is disgusting. Find instead the input type submit in the form
# with action ending in "/login".
print("Click Login button...")
driver.find_element(By.XPATH, "//button[normalize-space()='Login']").click()
# Find the new post form, fill it in with our pictureand a caption.
print("Click on Create New Post...")
driver.find_element(By.LINK_TEXT, "Create New Post").click()
print("Add file to input element...")
driver.find_element(By.XPATH, "//input[@type='file']").send_keys(green_path)
print("Click on Post button...")
driver.find_element(By.LINK_TEXT, "Post").click()
# Wait until the post loads, and in particular its picture, then take a
# screenshot of the whole page.
print("Wait for post and image to be loaded...")
WebDriverWait(driver, timeout=10).until(
lambda d: d.execute_script(
"return arguments[0].complete",
d.find_element(
By.XPATH, "//div[@class='timeline-status-component-content']//img"
),
)
)
print("Take screenshot...")
driver.save_screenshot(screenshot_path)
# All done ^-^
driver.quit()
'';
in
pkgs.nixosTest {
name = "test-pixelfed-garage";
nodes = {
server = { config, ... }: {
virtualisation = {
memorySize = lib.mkVMOverride 8192;
cores = 8;
};
imports = with self.nixosModules; [ garage pixelfed pixelfed-vm ];
# TODO: pair down
environment.systemPackages = with pkgs; [
python3
chromium
xh
seleniumScript
helix
imagemagick
];
environment.variables = {
POST_MEDIA = ./green.png;
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret;
};
};
};
testScript = ''
import re
# import time
server.start()
with subtest("Pixelfed starts"):
server.wait_for_unit("phpfpm-pixelfed.service")
# make sure pixelfed is fully up and running before we interact with it
# TODO: is there a way to test for this?
# REVIEW: this is copied from the Mastodon test; Pixelfed might be faster to start up.
# time.sleep(180)
# # REVIEW: not sure why necessary. if it is, could we push this into the installation?
# # FIXME: this is an interactive command; look into flags to make it non-interactive
# with subtest("Passport install"):
# server.succeed("pixelfed-manage passport:install")
with subtest("Account creation"):
password = "testtest"
server.succeed(f"pixelfed-manage user:create --name=test --username=test --email=test@test.com --password={password} --confirm_email=1")
# # REVIEW: do we actually need TTY?
# with subtest("TTY Login"):
# server.wait_until_tty_matches("1", "login: ")
# server.send_chars("root\n");
# with subtest("Log in with toot"):
# # toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt
# server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n")
# server.wait_until_tty_matches("1", "Password: ")
# server.send_chars(password + "\n")
# server.wait_until_tty_matches("1", "Successfully logged in.")
# with subtest("post text"):
# server.succeed("echo 'hello mastodon' | toot post")
# with subtest("post image"):
# server.succeed("toot post --media $POST_MEDIA")
# with subtest("access garage"):
# server.succeed("mc alias set garage http://s3.garage.localhost:3900 --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
# server.succeed("mc ls garage/mastodon")
# with subtest("access image in garage"):
# image = server.succeed("mc find garage --regex original")
# image = image.rstrip()
# if image == "":
# raise Exception("image posted to mastodon did not get stored in garage")
# server.succeed(f"mc cat {image} >/garage-image.webp")
# garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
# image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
# if garage_image_hash != image_hash:
# raise Exception("image stored in garage did not match image uploaded")
# with subtest("Content security policy allows garage images"):
# headers = server.succeed("xh -h http://masstodon.localhost:55001/public/local")
# csp_match = None
# # I can't figure out re.MULTILINE
# for header in headers.split("\n"):
# csp_match = re.match('^Content-Security-Policy: (.*)$', header)
# if csp_match is not None:
# break
# if csp_match is None:
# raise Exception("mastodon did not send a content security policy header")
# csp = csp_match.group(1)
# # the img-src content security policy should include the garage server
# garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost:3902.*", csp)
# if garage_csp is None:
# raise Exception("Mastodon's content security policy does not include garage server. image will not be displayed properly on mastodon.")
# NOTE: This could in theory give a false positive if pixelfed changes it's
# colorscheme to include pure green. (see same problem in mastodon-garage.nix).
# TODO: For instance: post a red image and check that the green pixel IS NOT
# there, then post a green image and check that the green pixel IS there.
with subtest("image displays"):
server.succeed(f"selenium-script test@test.com {password}")
server.copy_from_vm("/screenshot.png", "")
displayed_colors = server.succeed("convert /screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
# check that the green image displayed somewhere
green_check = re.match(".*#00FF00.*", displayed_colors, re.S)
if green_check is None:
raise Exception("cannot detect the uploaded image on pixelfed page.")
'';
}