# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2022 Huang Jianan <jnhuang95@gmail.com>
# Author: Huang Jianan <jnhuang95@gmail.com>

import os
import pytest
import shutil
import subprocess

EROFS_SRC_DIR = 'erofs_src_dir'
EROFS_IMAGE_NAME = 'erofs.img'

def generate_file(name, size):
    """
    Generates a file filled with 'x'.
    """
    content = 'x' * size
    file = open(name, 'w')
    file.write(content)
    file.close()

def make_erofs_image(build_dir):
    """
    Makes the EROFS images used for the test.

    The image is generated at build_dir with the following structure:
    erofs_src_dir/
    ├── f4096
    ├── f7812
    ├── subdir/
    │   └── subdir-file
    ├── symdir -> subdir
    └── symfile -> f5096
    """
    root = os.path.join(build_dir, EROFS_SRC_DIR)
    os.makedirs(root)

    # 4096: uncompressed file
    generate_file(os.path.join(root, 'f4096'), 4096)

    # 7812: Compressed file
    generate_file(os.path.join(root, 'f7812'), 7812)

    # sub-directory with a single file inside
    subdir_path = os.path.join(root, 'subdir')
    os.makedirs(subdir_path)
    generate_file(os.path.join(subdir_path, 'subdir-file'), 100)

    # symlink
    os.symlink('subdir', os.path.join(root, 'symdir'))
    os.symlink('f7812', os.path.join(root, 'symfile'))

    input_path = os.path.join(build_dir, EROFS_SRC_DIR)
    output_path = os.path.join(build_dir, EROFS_IMAGE_NAME)
    args = ' '.join([output_path, input_path])
    subprocess.run(['mkfs.erofs -zlz4 ' + args], shell=True, check=True,
                   stdout=subprocess.DEVNULL)

def clean_erofs_image(build_dir):
    """
    Deletes the image and src_dir at build_dir.
    """
    path = os.path.join(build_dir, EROFS_SRC_DIR)
    shutil.rmtree(path)
    image_path = os.path.join(build_dir, EROFS_IMAGE_NAME)
    os.remove(image_path)

def erofs_ls_at_root(u_boot_console):
    """
    Test if all the present files and directories were listed.
    """
    no_slash = u_boot_console.run_command('erofsls host 0')
    slash = u_boot_console.run_command('erofsls host 0 /')
    assert no_slash == slash

    expected_lines = ['./', '../', '4096   f4096', '7812   f7812', 'subdir/',
                      '<SYM>   symdir', '<SYM>   symfile', '4 file(s), 3 dir(s)']

    output = u_boot_console.run_command('erofsls host 0')
    for line in expected_lines:
        assert line in output

def erofs_ls_at_subdir(u_boot_console):
    """
    Test if the path resolution works.
    """
    expected_lines = ['./', '../', '100   subdir-file', '1 file(s), 2 dir(s)']
    output = u_boot_console.run_command('erofsls host 0 subdir')
    for line in expected_lines:
        assert line in output

def erofs_ls_at_symlink(u_boot_console):
    """
    Test if the symbolic link's target resolution works.
    """
    output = u_boot_console.run_command('erofsls host 0 symdir')
    output_subdir = u_boot_console.run_command('erofsls host 0 subdir')
    assert output == output_subdir

    expected_lines = ['./', '../', '100   subdir-file', '1 file(s), 2 dir(s)']
    for line in expected_lines:
        assert line in output

def erofs_ls_at_non_existent_dir(u_boot_console):
    """
    Test if the EROFS support will crash when get a nonexistent directory.
    """
    out_non_existent = u_boot_console.run_command('erofsls host 0 fff')
    out_not_dir = u_boot_console.run_command('erofsls host 0 f1000')
    assert out_non_existent == out_not_dir
    assert '' in out_non_existent

def erofs_load_files(u_boot_console, files, sizes, address):
    """
    Loads files and asserts their checksums.
    """
    build_dir = u_boot_console.config.build_dir
    for (file, size) in zip(files, sizes):
        out = u_boot_console.run_command('erofsload host 0 {} {}'.format(address, file))

        # check if the right amount of bytes was read
        assert size in out

        # calculate u-boot file's checksum
        out = u_boot_console.run_command('md5sum {} {}'.format(address, hex(int(size))))
        u_boot_checksum = out.split()[-1]

        # calculate original file's checksum
        original_file_path = os.path.join(build_dir, EROFS_SRC_DIR + '/' + file)
        out = subprocess.run(['md5sum ' + original_file_path], shell=True, check=True,
                             capture_output=True, text=True)
        original_checksum = out.stdout.split()[0]

        # compare checksum
        assert u_boot_checksum == original_checksum

def erofs_load_files_at_root(u_boot_console):
    """
    Test load file from the root directory.
    """
    files = ['f4096', 'f7812']
    sizes = ['4096', '7812']
    address = '$kernel_addr_r'
    erofs_load_files(u_boot_console, files, sizes, address)

def erofs_load_files_at_subdir(u_boot_console):
    """
    Test load file from the subdirectory.
    """
    files = ['subdir/subdir-file']
    sizes = ['100']
    address = '$kernel_addr_r'
    erofs_load_files(u_boot_console, files, sizes, address)

def erofs_load_files_at_symlink(u_boot_console):
    """
    Test load file from the symlink.
    """
    files = ['symfile']
    sizes = ['7812']
    address = '$kernel_addr_r'
    erofs_load_files(u_boot_console, files, sizes, address)

def erofs_load_non_existent_file(u_boot_console):
    """
    Test if the EROFS support will crash when load a nonexistent file.
    """
    address = '$kernel_addr_r'
    file = 'non-existent'
    out = u_boot_console.run_command('erofsload host 0 {} {}'.format(address, file))
    assert 'Failed to load' in out

def erofs_run_all_tests(u_boot_console):
    """
    Runs all test cases.
    """
    erofs_ls_at_root(u_boot_console)
    erofs_ls_at_subdir(u_boot_console)
    erofs_ls_at_symlink(u_boot_console)
    erofs_ls_at_non_existent_dir(u_boot_console)
    erofs_load_files_at_root(u_boot_console)
    erofs_load_files_at_subdir(u_boot_console)
    erofs_load_files_at_symlink(u_boot_console)
    erofs_load_non_existent_file(u_boot_console)

@pytest.mark.boardspec('sandbox')
@pytest.mark.buildconfigspec('cmd_fs_generic')
@pytest.mark.buildconfigspec('cmd_erofs')
@pytest.mark.buildconfigspec('fs_erofs')
@pytest.mark.requiredtool('mkfs.erofs')
@pytest.mark.requiredtool('md5sum')

def test_erofs(u_boot_console):
    """
    Executes the erofs test suite.
    """
    build_dir = u_boot_console.config.build_dir

    try:
        # setup test environment
        make_erofs_image(build_dir)
        image_path = os.path.join(build_dir, EROFS_IMAGE_NAME)
        u_boot_console.run_command('host bind 0 {}'.format(image_path))
        # run all tests
        erofs_run_all_tests(u_boot_console)
    except:
        clean_erofs_image(build_dir)
        raise AssertionError

    # clean test environment
    clean_erofs_image(build_dir)