#!/usr/bin/python
# Copyright 2014 Google.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Unit tests for encoder module."""

import encoder_configuration
import os
import re
import shutil
import test_tools
import unittest

import encoder


class DummyCodec(encoder.Codec):
  def __init__(self):
    super(DummyCodec, self).__init__('dummy')
    self.extension = 'fake'
    self.option_set = encoder.OptionSet(
      encoder.Option('score', ['0', '5', '10']),
    )

  def StartEncoder(self, context):
    return encoder.Encoder(context,
                           encoder.OptionValueSet(self.option_set,
                                                  "--score=5"))

  def Execute(self, parameters, rate, videofile, workdir):
    # rate is unused. This is known.
    # pylint: disable=W0613
    match = re.search(r'--score=(\d+)', parameters.ToString())
    if match:
      return {'psnr': int(match.group(1)), 'bitrate': 100}
    else:
      return {'psnr': -100, 'bitrate': 100}

def Returns1(target_bitrate, result):
  """Score function that returns a constant value."""
  # pylint: disable=W0613
  return 1.0


class StorageOnlyCodec(object):
  """A codec that is only useful for testing storage."""
  def __init__(self):
    self.name = 'unittest'
    self.option_set = encoder.OptionSet()
    self.option_formatter = encoder.OptionFormatter(prefix='--', infix=':')

  def SpeedGroup(self, bitrate):
    # pylint: disable=R0201
    return str(bitrate)

  def ConfigurationFixups(self, parameters):
    # pylint: disable=R0201
    return parameters

class StorageOnlyCodecWithNoBitrate(StorageOnlyCodec):
  def __init__(self):
    super(StorageOnlyCodecWithNoBitrate, self).__init__()
    self.name = 'merged-bitrate'

  def SpeedGroup(self, bitrate):
    # pylint: disable=R0201
    return 'all'


class StorageOnlyContext(object):
  """A context that is only useful for testing storage."""
  def __init__(self):
    self.codec = StorageOnlyCodec()
    self.cache = None


class DummyVideofile(encoder.Videofile):
  def __init__(self, filename, clip_time):
    super(DummyVideofile, self).__init__(filename)
    self.clip_time = clip_time

  def ClipTime(self):
    return self.clip_time


class TestConfig(unittest.TestCase):

  def test_ChoiceOption(self):
    option = encoder.ChoiceOption(['foo', 'bar'])
    # Check FlagIsValidValue function.
    self.assertFalse(option.FlagIsValidValue('baz'))
    self.assertTrue(option.FlagIsValidValue('foo'))

  def test_IntegerOption(self):
    option = encoder.IntegerOption('foo', 5, 6)
    self.assertTrue(str(6) in option.values)
    self.assertFalse(str(4) in option.values)


class TestOptionSet(unittest.TestCase):
  def test_InitNoArgs(self):
    opts = encoder.OptionSet()
    self.assertFalse(opts.HasOption('foo'))

  def test_InitSingle(self):
    opts = encoder.OptionSet(encoder.Option('foo', ['foo', 'bar']))
    self.assertTrue(opts.Option('foo'))
    self.assertFalse(opts.HasOption('bar'))

  def test_InitMany(self):
    opts = encoder.OptionSet(encoder.Option('foo', ['foo', 'bar']),
                             encoder.Option('bar', ['bar', 'baz']))
    self.assertTrue(opts.Option('foo'))
    self.assertTrue(opts.Option('bar'))
    self.assertFalse(opts.HasOption('baz'))

  def test_RegisterOption(self):
    opts = encoder.OptionSet()
    self.assertFalse(opts.HasOption('foo'))
    opts.RegisterOption(encoder.Option('foo', ['foo', 'bar']))
    self.assertTrue(opts.HasOption('foo'))
    self.assertTrue(opts.Option('foo'))

  def test_LockOption(self):
    opts = encoder.OptionSet(encoder.Option('foo', ['value1', 'value2']))
    self.assertEqual(2, len(opts.Option('foo').values))
    self.assertTrue(opts.Option('foo').CanChange())
    opts.LockOption('foo', 'value1')
    self.assertTrue(opts.Option('foo').mandatory)
    self.assertEqual(1, len(opts.Option('foo').values))
    self.assertFalse(opts.Option('foo').CanChange())

  def test_FindFlagOption(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']))
    self.assertIsNone(opts.FindFlagOption('baz'))
    self.assertEquals('foo/bar', opts.FindFlagOption('foo').name)

  def test_Format(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']))
    self.assertEquals('--foo', opts.Format('foo/bar', 'foo',
                                           encoder.OptionFormatter()))

  def test_Mandatory(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']))
    self.assertFalse(opts.AllMandatoryOptions())
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']).Mandatory())
    self.assertEquals(set([opt.name for opt in opts.AllMandatoryOptions()]),
                      set(['foo/bar']))


class TestOptionValueSet(unittest.TestCase):
  def test_ReproduceOneArg(self):
    valueset = encoder.OptionValueSet(encoder.OptionSet(), '--foo=bar')
    self.assertEqual('--foo=bar', valueset.ToString())

  def test_GetValue(self):
    valueset = encoder.OptionValueSet(
      encoder.OptionSet(encoder.Option('foo', ['baz', 'bar'])), '--foo=bar')
    self.assertEqual('bar', valueset.GetValue('foo'))

  def test_GetValueNotPresent(self):
    option = encoder.Option('foo', ['foo', 'bar'])
    config = encoder.OptionValueSet(encoder.OptionSet(option), '--notfoo=foo')
    with self.assertRaises(encoder.Error):
      config.GetValue('foo')

  def test_ReproduceFlag(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo']))
    valueset = encoder.OptionValueSet(opts, '--foo')
    self.assertEqual('--foo', valueset.ToString())

  def test_UnknownFlagPreserved(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo']))
    valueset = encoder.OptionValueSet(opts, '--bar')
    self.assertEqual('--bar', valueset.ToString())

  def test_FlagsSorted(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo']))
    valueset = encoder.OptionValueSet(opts, '--foo --bar')
    self.assertEqual('--bar --foo', valueset.ToString())

  def test_ChangeValue(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']))
    valueset = encoder.OptionValueSet(opts, '--foo')
    newset = valueset.ChangeValue('foo/bar', 'bar')
    self.assertEqual('--bar', newset.ToString())
    # Check that old set is not modified.
    self.assertEqual('--foo', valueset.ToString())

  def test_ChangeValueOfUnknownOption(self):
    opts = encoder.OptionSet(encoder.ChoiceOption(['foo', 'bar']))
    valueset = encoder.OptionValueSet(opts, '--foo')
    with self.assertRaises(encoder.Error):
      # pylint: disable=W0612
      newset = valueset.ChangeValue('nosuchname', 'bar')

  def test_RandomlyPatchConfig(self):
    config = encoder.OptionValueSet(
      encoder.OptionSet(encoder.Option('foo', ['foo', 'bar'])),
      '--foo=foo')
    newconfig = config.RandomlyPatchConfig()
    # There is only one possible change. It should be chosen.
    self.assertEqual(newconfig.ToString(), '--foo=bar')
    # Test case where original set did not have value.
    config = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar'])),
        '')
    newconfig = config.RandomlyPatchConfig()
    self.assertIn(newconfig.ToString(), ['--foo=foo', '--foo=bar'])

  def test_OtherFormatter(self):
    valueset = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar'])),
        '-foo foo',
        formatter=encoder.OptionFormatter(prefix='-', infix=' '))
    self.assertEqual('-foo foo', valueset.ToString())
    valueset = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar']),
                          encoder.Option('xyz', ['abc', 'def'])),
        '-foo foo -xyz abc',
        formatter=encoder.OptionFormatter(prefix='-', infix=' '))
    self.assertEqual('-foo foo -xyz abc', valueset.ToString())

  def test_RandomlyRemoveParameterSuccessfully(self):
    config = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar'])),
        '--foo=foo')
    newconfig = config.RandomlyRemoveParameter()
    self.assertEqual('', newconfig.ToString())

  def test_RandomlyRemoveParameterWithOnlyMandatory(self):
    config = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar']).Mandatory()),
        '--foo=foo')
    newconfig = config.RandomlyRemoveParameter()
    self.assertFalse(newconfig)

  def test_MissingMandatoryFailsToParse(self):
    options = encoder.OptionSet(
        encoder.Option('foo', ['foo', 'bar']).Mandatory())
    with self.assertRaises(encoder.ParseError):
      encoder.OptionValueSet(options, '')

  def test_UnlistedValueFailsToParse(self):
    options = encoder.OptionSet(
        encoder.Option('foo', ['foo', 'bar']).Mandatory())
    with self.assertRaises(encoder.ParseError):
      encoder.OptionValueSet(options, '--foo=nonsense')


class TestCodec(unittest.TestCase):
  def setUp(self):
    self.videofile = DummyVideofile('foofile_640_480_30.yuv', clip_time=1)

  def test_DisplayHeading(self):
    codec = DummyCodec()
    self.assertEqual('score', codec.DisplayHeading())

  def test_RandomlyChangeConfig(self):
    codec = DummyCodec()
    otherconfig = codec.RandomlyChangeConfig(
      encoder.OptionValueSet(codec.option_set, '--score=5'))
    self.assertNotEqual(otherconfig, '--score=5')
    self.assertIn(otherconfig, ['--score=0', '--score=10'])

  def test_FormatterExists(self):
    codec = DummyCodec()
    self.assertTrue(codec.option_formatter)

  def test_EncoderVersionExists(self):
    codec = DummyCodec()
    self.assertRegexpMatches(codec.EncoderVersion(), r'^compare-codecs')


class TestEncoder(unittest.TestCase):
  def test_OptionValues(self):
    codec = DummyCodec()
    my_encoder = encoder.Encoder(encoder.Context(codec),
      encoder.OptionValueSet(encoder.OptionSet(
            encoder.IntegerOption('score', 0, 100)), '--score=77'))
    self.assertEqual(repr(my_encoder.OptionValues()), "{'score': '77'}")
    self.assertEqual(my_encoder.DisplayValues(), '77')

  def test_ParametersCanBeStoredAndRetrieved(self):
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    my_encoder.Store()
    filename = my_encoder.Hashname()
    next_encoder = encoder.Encoder(context, filename=filename)
    self.assertEqual(my_encoder.parameters, next_encoder.parameters)

  def test_ParametersCanChangeMayReturnTrue(self):
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(context,
        encoder.OptionValueSet(encoder.OptionSet(
            encoder.Option('key', ['value1', 'value2'])),
                               '--parameters'))
    self.assertTrue(my_encoder.ParametersCanChange())

  def test_ParametersCanChangeMayReturnFalse(self):
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    self.assertFalse(my_encoder.ParametersCanChange())

  def testInitFromFile(self):
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    my_encoder.Store()
    new_encoder = encoder.Encoder(context, filename=my_encoder.Hashname())
    self.assertEquals(new_encoder.parameters, my_encoder.parameters)

  def testInitFromBrokenFile(self):
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    my_encoder.Store()
    # Break stored object. Note: This uses knowledge of the memory cache.
    old_filename = my_encoder.Hashname()
    parameters = context.cache.encoders[old_filename].parameters
    parameters.other_parts.append('--extra-stuff')
    # Now Hashname() should return a different value.
    with self.assertRaisesRegexp(encoder.Error, 'contains wrong arguments'):
      # pylint: disable=W0612
      new_encoder = encoder.Encoder(context, filename=old_filename)

  def test_Changevalue(self):
    config = encoder.OptionValueSet(
        encoder.OptionSet(encoder.Option('foo', ['foo', 'bar'])),
        '--foo=foo')
    context = encoder.Context(DummyCodec())
    my_encoder = encoder.Encoder(context, config)
    next_encoder = my_encoder.ChangeValue('foo', 'bar')
    self.assertEquals(next_encoder.parameters, '--foo=bar')


class TestEncoding(unittest.TestCase):

  def testGenerateSomeUntriedVariants(self):
    context = encoder.Context(DummyCodec())
    my_encoder = context.codec.StartEncoder(context)
    videofile = DummyVideofile('foofile_640_480_30.yuv', clip_time=1)
    encoding = my_encoder.Encoding(1000, videofile)
    # The dummy codec has a parameter with multiple possible values,
    # so at least some variants should be returned.
    variants = encoding.SomeUntriedVariants()
    self.assertTrue(variants)

  def testGenerateUntriedVariantsUntilNoneFound(self):
    context = encoder.Context(DummyCodec())
    my_encoder = context.codec.StartEncoder(context)
    videofile = DummyVideofile('foofile_640_480_30.yuv', clip_time=1)
    encoding = my_encoder.Encoding(1000, videofile)
    variants = encoding.SomeUntriedVariants()
    # Keep generating variants until we run out. This should happen
    # after 3 variants for the Dummy codec.
    variant_count = 0
    while variants:
      for variant in variants:
        variant.Execute().Store()
        variant_count += 1
      variants = encoding.SomeUntriedVariants()
    # We cannot guarantee that all 3 are found, since the process
    # is random, but no more than 3 should be found.
    self.assertGreaterEqual(3, variant_count)

  def testReadResultWithoutFrameData(self):
    context = encoder.Context(DummyCodec())
    my_encoder = context.codec.StartEncoder(context)
    videofile = DummyVideofile('foofile_640_480_30.yuv', clip_time=1)
    encoding = my_encoder.Encoding(1000, videofile)
    encoding.result = {'foo': 5, 'frame': ['first', 'second']}
    self.assertEqual({'foo': 5}, encoding.ResultWithoutFrameData())


class TestVideofile(unittest.TestCase):
  def testMpegFormatName(self):
    videofile = encoder.Videofile('test_640x480_20.yuv')
    self.assertEqual(640, videofile.width)
    self.assertEqual(480, videofile.height)
    self.assertEqual(20, videofile.framerate)

  def testMpegFormatWithTrailer(self):
    videofile = encoder.Videofile('test_640x480_20_part.yuv')
    self.assertEqual(640, videofile.width)
    self.assertEqual(480, videofile.height)
    self.assertEqual(20, videofile.framerate)

  def testGoogleFormatName(self):
    videofile = encoder.Videofile('test_640_480_20.yuv')
    self.assertEqual(640, videofile.width)
    self.assertEqual(480, videofile.height)
    self.assertEqual(20, videofile.framerate)

  def testBrokenName(self):
    with self.assertRaises(Exception):
      encoder.Videofile('no_numbers_here.yuv')


class TestEncodingDiskCache(test_tools.FileUsingCodecTest):
  def testInit(self):
    cache = encoder.EncodingDiskCache(StorageOnlyContext())
    self.assertTrue(cache)

  def testSearchPathForScores(self):
    # The score path starts out empty from setUp.
    cache = encoder.EncodingDiskCache(StorageOnlyContext())
    path = cache.SearchPathForScores()
    self.assertEquals(1, len(path),
                      msg='Not just one element when scorepath is empty')
    encoder_configuration.conf.override_scorepath_for_test(['foo'])
    path = cache.SearchPathForScores()
    self.assertEquals(2, len(path),
                      msg='Not just 2 elements when scorepath has 1 element')

  def testStoreFetchEncoder(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    new_encoder_data = cache.ReadEncoderParameters(
        os.path.join(cache.WorkDir(), my_encoder.Hashname()))
    self.assertEquals(new_encoder_data, my_encoder.parameters)
    # Using only the hashname should work too.
    new_encoder_data = cache.ReadEncoderParameters(my_encoder.Hashname())
    self.assertEquals(new_encoder_data, my_encoder.parameters)

  def testStoreFetchEncoding(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    my_encoding = encoder.Encoding(my_encoder, 123,
                                   encoder.Videofile('x/foo_640_480_20.yuv'))
    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    my_encoding.result = None
    result = cache.ReadEncodingResult(my_encoding)
    self.assertEquals(result, testresult)

  def testStoreMultipleEncodings(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    # This particular test needs the context to know about the cache.
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)

    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    my_encoding = encoder.Encoding(my_encoder, 246, videofile)
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    result = cache.AllScoredRates(my_encoder, videofile)
    self.assertEquals(2, len(result))
    result = cache.AllScoredEncodings(123, videofile)
    self.assertEquals(1, len(result))

  def testAllEncoderFilenames(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    files = cache.AllEncoderFilenames()
    self.assertEquals(0, len(files))
    my_encoder = encoder.Encoder(
      context,
      encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    files = cache.AllEncoderFilenames()
    self.assertEquals(1, len(files))
    self.assertEquals(my_encoder.Hashname(), os.path.basename(files[0]))

  def testRemoveEncoder(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    my_encoder = encoder.Encoder(
      context,
      encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    files = cache.AllEncoderFilenames()
    self.assertEquals(1, len(files))
    self.assertEquals(my_encoder.Hashname(), os.path.basename(files[0]))
    cache.RemoveEncoder(my_encoder.Hashname())
    files = cache.AllEncoderFilenames()
    self.assertEquals(0, len(files))

  def testReadResultFromAlternateDir(self):
    context = StorageOnlyContext()
    otherdir_path = os.path.join(encoder_configuration.conf.sysdir(),
                                 'otherdir')
    os.mkdir(otherdir_path)
    cache = encoder.EncodingDiskCache(context)
    other_cache = encoder.EncodingDiskCache(context, scoredir='otherdir')
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)

    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    my_encoding.result = None
    result = other_cache.ReadEncodingResult(my_encoding)
    self.assertIsNone(result)
    shutil.rmtree(otherdir_path)
    shutil.copytree(encoder_configuration.conf.workdir(), otherdir_path)
    result = other_cache.ReadEncodingResult(my_encoding)
    self.assertEquals(result, testresult)

  def testAllScoredEncodingsForEncoder(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    # This particular test needs the context to know about the cache.
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    # Cache should start off empty.
    self.assertFalse(cache.AllScoredEncodingsForEncoder(my_encoder))
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)
    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    result = cache.AllScoredEncodingsForEncoder(my_encoder)
    self.assertTrue(result)
    self.assertEquals(1, len(result))
    # The resulting videofile should have a basename = filename,
    # because synthesizing filenames from result files loses directory
    # information.
    self.assertEquals('foo_640_480_20.yuv', result[0].videofile.filename)

  def testStorageWithMergedBitrates(self):
    context = StorageOnlyContext()
    context.codec = StorageOnlyCodecWithNoBitrate()
    cache = encoder.EncodingDiskCache(context)
    # This particular test needs the context to know about the cache.
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)

    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    my_encoding = encoder.Encoding(my_encoder, 246, videofile)
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    result = cache.AllScoredRates(my_encoder, videofile)
    self.assertEquals(1, len(result))
    result = cache.AllScoredEncodings(123, videofile)
    self.assertEquals(1, len(result))

  def testEncodersInMultipleRepos(self):
    test_tools.EmptyWorkDirectory()
    context = StorageOnlyContext()
    cache = encoder.EncodingDiskCache(context)
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    other_dir = os.path.join(encoder_configuration.conf.sysdir(),
                             'multirepo_test')
    os.mkdir(other_dir)
    other_cache = encoder.EncodingDiskCache(context, scoredir='multirepo_test')
    encoder_configuration.conf.override_scorepath_for_test([other_dir])
    other_cache.StoreEncoder(my_encoder)
    encoders = cache.AllEncoderFilenames(only_workdir=True)
    self.assertEquals(0, len(encoders))
    encoders = cache.AllEncoderFilenames(only_workdir=False)
    self.assertEquals(1, len(encoders))
    fetched_encoder = encoder.Encoder(context, filename=encoders[0])
    self.assertEquals(my_encoder.parameters.ToString(),
                      fetched_encoder.parameters.ToString())
    my_encoding = encoder.Encoding(my_encoder, 123,
                                   encoder.Videofile('x/foo_640_480_20.yuv'))
    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    other_cache.StoreEncoding(my_encoding)
    # With a specified directory, we should find it in only one place.
    self.assertTrue(other_cache.ReadEncodingResult(my_encoding))
    # Without a specified directory, we should find it on the searchpath.
    self.assertTrue(cache.ReadEncodingResult(my_encoding))
    # Without a searchpath, we shouldn't find it in the default cache.
    encoder_configuration.conf.override_scorepath_for_test([])
    self.assertFalse(cache.ReadEncodingResult(my_encoding))

  def testBrokenStoredEncoding(self):
    context = StorageOnlyContext()
    other_dir = os.path.join(encoder_configuration.conf.sysdir(),
                             'broken_files')
    os.mkdir(other_dir)

    cache = encoder.EncodingDiskCache(context, scoredir='broken_files')
    # This particular test needs the context to know about the cache.
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    # Cache should start off empty.
    self.assertFalse(cache.AllScoredEncodingsForEncoder(my_encoder))
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)
    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    # TODO(hta): Expose the filename generation as a function for testing.
    with open(os.path.join(cache.workdir,
                           my_encoding.encoder.Hashname(),
                           cache.context.codec.SpeedGroup(my_encoding.bitrate),
                           '%s.result' % my_encoding.videofile.basename),
              'w') as scorefile:
      scorefile.write('stuff that is not valid json')

    result = cache.AllScoredEncodingsForEncoder(my_encoder)
    self.assertFalse(result)
    self.assertEquals(1, len(cache.bad_encodings))


class TestEncodingMemoryCache(unittest.TestCase):
  def testStoreMultipleEncodings(self):
    context = StorageOnlyContext()
    cache = encoder.EncodingMemoryCache(context)
    # This particular test needs the context to know about the cache.
    context.cache = cache
    my_encoder = encoder.Encoder(
        context,
        encoder.OptionValueSet(encoder.OptionSet(), '--parameters'))
    cache.StoreEncoder(my_encoder)
    videofile = encoder.Videofile('x/foo_640_480_20.yuv')
    my_encoding = encoder.Encoding(my_encoder, 123, videofile)

    testresult = {'foo': 'bar'}
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    my_encoding = encoder.Encoding(my_encoder, 246, videofile)
    my_encoding.result = testresult
    cache.StoreEncoding(my_encoding)
    result = cache.AllScoredRates(my_encoder, videofile)
    self.assertEquals(2, len(result))
    result = cache.AllScoredEncodings(123, videofile)
    self.assertEquals(1, len(result))
    # Verify that it's working correctly with a new videofile object.
    videofile2 = encoder.Videofile(videofile.filename)
    result = cache.AllScoredEncodings(123, videofile2)
    my_encoding = encoder.Encoding(my_encoder, 123, videofile2)
    self.assertTrue(cache.ReadEncodingResult(my_encoding))
    # Verify that it's working correctly with an encoder created via hashname.
    encoder2 = encoder.Encoder(context, filename=my_encoder.Hashname())
    encoding2 = encoder2.Encoding(123, videofile2)
    self.assertTrue(cache.ReadEncodingResult(encoding2))


if __name__ == '__main__':
  unittest.main()