普通文本  |  154行  |  6.78 KB

# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import collections
import logging
import os
import tempfile
from autotest_lib.client.common_lib import error
from autotest_lib.server import test
from autotest_lib.server import utils


class brillo_HWRandom(test.test):
    """Tests that /dev/hw_random is present and passes basic tests."""
    version = 1

    # Basic info for a dieharder test.
    TestInfo = collections.namedtuple('TestInfo', 'number custom_args')

    # Basic results of a dieharder test.
    TestResult = collections.namedtuple('TestResult', 'test_name assessment')

    # Results of a test suite run.
    TestSuiteResult = collections.namedtuple('TestSuiteResult',
                                             'num_weak num_failed full_output')

    # A list of dieharder tests that can be reasonably constrained to run within
    # a sample space of <= 10MB, and the arguments to constrain them. These have
    # been applied somewhat naively and over time these can be tweaked if a test
    # has a problematic failure rate. In general, since there is only so much
    # that can be done within the constraints these tests should be viewed as a
    # sanity check and not as a measure of entropy quality. If a hardware RNG
    # repeatedly fails this test, it has a big problem and should not be used.
    _TEST_LIST = [
        TestInfo(number=0, custom_args=['-p', '50']),
        TestInfo(number=1, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=2, custom_args=['-p', '50', '-t', '1000']),
        TestInfo(number=3, custom_args=['-p', '50', '-t', '5000']),
        TestInfo(number=8, custom_args=['-p', '40']),
        TestInfo(number=10, custom_args=[]),
        TestInfo(number=11, custom_args=[]),
        TestInfo(number=12, custom_args=[]),
        TestInfo(number=15, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=16, custom_args=['-p', '50', '-t', '7000']),
        TestInfo(number=17, custom_args=['-p', '50', '-t', '20000']),
        TestInfo(number=100, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=101, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=102, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=200, custom_args=['-p', '20', '-t', '20000',
                                          '-n', '3']),
        TestInfo(number=202, custom_args=['-p', '20', '-t', '20000']),
        TestInfo(number=203, custom_args=['-p', '50', '-t', '50000']),
        TestInfo(number=204, custom_args=['-p', '200']),
        TestInfo(number=205, custom_args=['-t', '512000']),
        TestInfo(number=206, custom_args=['-t', '40000', '-n', '64']),
        TestInfo(number=207, custom_args=['-t', '300000']),
        TestInfo(number=208, custom_args=['-t', '400000']),
        TestInfo(number=209, custom_args=['-t', '2000000']),
    ]

    def _run_dieharder_test(self, input_file, test_number, custom_args=None):
        """Runs a specific dieharder test (locally) and returns the assessment.

        @param input_file: The name of the file containing the data to be tested
        @param test_number: A dieharder test number specifying which test to run
        @param custom_args: Optional additional arguments for the test

        @returns A list of TestResult

        @raise TestError: An error occurred running the test.
        """
        command = ['dieharder',
                   '-g', '201',
                   '-D', 'test_name',
                   '-D', 'ntuple',
                   '-D', 'assessment',
                   '-D', '32768',  # no_whitespace
                   '-c', ',',
                   '-d', str(test_number),
                   '-f', input_file]
        if custom_args:
            command.extend(custom_args)
        command_result = utils.run(command)
        if command_result.stderr != '':
            raise error.TestError('Error running dieharder: %s' %
                                  command_result.stderr.rstrip())
        output = command_result.stdout.splitlines()
        results = []
        for line in output:
            fields = line.split(',')
            if len(fields) != 3:
                raise error.TestError(
                    'dieharder: unexpected output: %s' % line)
            results.append(self.TestResult(
                test_name='%s[%s]' % (fields[0], fields[1]),
                assessment=fields[2]))
        return results


    def _run_all_dieharder_tests(self, input_file):
        """Runs all the dieharder tests in _TEST_LIST, continuing on failure.

        @param input_file: The name of the file containing the data to be tested

        @returns TestSuiteResult

        @raise TestError: An error occurred running the test.
        """
        weak = 0
        failed = 0
        full_output = 'Test Results:\n'
        for test_info in self._TEST_LIST:
            results = self._run_dieharder_test(input_file,
                                               test_info.number,
                                               test_info.custom_args)
            for test_result in results:
                logging.info('%s: %s', test_result.test_name,
                             test_result.assessment)
                full_output += '  %s: %s\n' % test_result
                if test_result.assessment == 'WEAK':
                    weak += 1
                elif test_result.assessment == 'FAILED':
                    failed += 1
                elif test_result.assessment != 'PASSED':
                    raise error.TestError(
                        'Unexpected output: %s' % full_output)
        logging.info('Total: %d, Weak: %d, Failed: %d',
                     len(self._TEST_LIST), weak, failed)
        return self.TestSuiteResult(weak, failed, full_output)

    def run_once(self, host=None):
        """Runs the test.

        @param host: A host object representing the DUT.

        @raise TestError: An error occurred running the test.
        @raise TestFail: The test ran without error but failed.
        """
        # Grab 10MB of data from /dev/hw_random.
        dut_file = '/data/local/tmp/hw_random_output'
        host.run('dd count=20480 if=/dev/hw_random of=%s' % dut_file)
        with tempfile.NamedTemporaryFile() as local_file:
            host.get_file(dut_file, local_file.name)
            output_size = os.stat(local_file.name).st_size
            if output_size != 0xA00000:
                raise error.TestError(
                    'Unexpected output length: %d (expecting %d)',
                    output_size, 0xA00000)
            # Run the data through each test (even if one fails).
            result = self._run_all_dieharder_tests(local_file.name)
            if result.num_failed > 0 or result.num_weak > 5:
                raise error.TestFail(result.full_output)