#!/usr/bin/env python3
import os
import unittest
import tempfile
import subprocess
import openpilot . system . hardware . tici . casync as casync
# dd if=/dev/zero of=/tmp/img.raw bs=1M count=2
# sudo losetup -f /tmp/img.raw
# losetup -a | grep img.raw
LOOPBACK = os . environ . get ( ' LOOPBACK ' , None )
class TestCasync ( unittest . TestCase ) :
@classmethod
def setUpClass ( cls ) :
cls . tmpdir = tempfile . TemporaryDirectory ( )
# Build example contents
chunk_a = [ i % 256 for i in range ( 1024 ) ] * 512
chunk_b = [ ( 256 - i ) % 256 for i in range ( 1024 ) ] * 512
zeroes = [ 0 ] * ( 1024 * 128 )
contents = chunk_a + chunk_b + zeroes + chunk_a
cls . contents = bytes ( contents )
# Write to file
cls . orig_fn = os . path . join ( cls . tmpdir . name , ' orig.bin ' )
with open ( cls . orig_fn , ' wb ' ) as f :
f . write ( cls . contents )
# Create casync files
cls . manifest_fn = os . path . join ( cls . tmpdir . name , ' orig.caibx ' )
cls . store_fn = os . path . join ( cls . tmpdir . name , ' store ' )
subprocess . check_output ( [ " casync " , " make " , " --compression=xz " , " --store " , cls . store_fn , cls . manifest_fn , cls . orig_fn ] )
target = casync . parse_caibx ( cls . manifest_fn )
hashes = [ c . sha . hex ( ) for c in target ]
# Ensure we have chunk reuse
assert len ( hashes ) > len ( set ( hashes ) )
def setUp ( self ) :
# Clear target_lo
if LOOPBACK is not None :
self . target_lo = LOOPBACK
with open ( self . target_lo , ' wb ' ) as f :
f . write ( b " 0 " * len ( self . contents ) )
self . target_fn = os . path . join ( self . tmpdir . name , next ( tempfile . _get_candidate_names ( ) ) )
self . seed_fn = os . path . join ( self . tmpdir . name , next ( tempfile . _get_candidate_names ( ) ) )
def tearDown ( self ) :
for fn in [ self . target_fn , self . seed_fn ] :
try :
os . unlink ( fn )
except FileNotFoundError :
pass
def test_simple_extract ( self ) :
target = casync . parse_caibx ( self . manifest_fn )
sources = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_fn )
with open ( self . target_fn , ' rb ' ) as target_f :
self . assertEqual ( target_f . read ( ) , self . contents )
self . assertEqual ( stats [ ' remote ' ] , len ( self . contents ) )
def test_seed ( self ) :
target = casync . parse_caibx ( self . manifest_fn )
# Populate seed with half of the target contents
with open ( self . seed_fn , ' wb ' ) as seed_f :
seed_f . write ( self . contents [ : len ( self . contents ) / / 2 ] )
sources = [ ( ' seed ' , casync . FileChunkReader ( self . seed_fn ) , casync . build_chunk_dict ( target ) ) ]
sources + = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_fn )
with open ( self . target_fn , ' rb ' ) as target_f :
self . assertEqual ( target_f . read ( ) , self . contents )
self . assertGreater ( stats [ ' seed ' ] , 0 )
self . assertLess ( stats [ ' remote ' ] , len ( self . contents ) )
def test_already_done ( self ) :
""" Test that an already flashed target doesn ' t download any chunks """
target = casync . parse_caibx ( self . manifest_fn )
with open ( self . target_fn , ' wb ' ) as f :
f . write ( self . contents )
sources = [ ( ' target ' , casync . FileChunkReader ( self . target_fn ) , casync . build_chunk_dict ( target ) ) ]
sources + = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_fn )
with open ( self . target_fn , ' rb ' ) as f :
self . assertEqual ( f . read ( ) , self . contents )
self . assertEqual ( stats [ ' target ' ] , len ( self . contents ) )
def test_chunk_reuse ( self ) :
""" Test that chunks that are reused are only downloaded once """
target = casync . parse_caibx ( self . manifest_fn )
# Ensure target exists
with open ( self . target_fn , ' wb ' ) :
pass
sources = [ ( ' target ' , casync . FileChunkReader ( self . target_fn ) , casync . build_chunk_dict ( target ) ) ]
sources + = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_fn )
with open ( self . target_fn , ' rb ' ) as f :
self . assertEqual ( f . read ( ) , self . contents )
self . assertLess ( stats [ ' remote ' ] , len ( self . contents ) )
@unittest . skipUnless ( LOOPBACK , " requires loopback device " )
def test_lo_simple_extract ( self ) :
target = casync . parse_caibx ( self . manifest_fn )
sources = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_lo )
with open ( self . target_lo , ' rb ' ) as target_f :
self . assertEqual ( target_f . read ( len ( self . contents ) ) , self . contents )
self . assertEqual ( stats [ ' remote ' ] , len ( self . contents ) )
@unittest . skipUnless ( LOOPBACK , " requires loopback device " )
def test_lo_chunk_reuse ( self ) :
""" Test that chunks that are reused are only downloaded once """
target = casync . parse_caibx ( self . manifest_fn )
sources = [ ( ' target ' , casync . FileChunkReader ( self . target_lo ) , casync . build_chunk_dict ( target ) ) ]
sources + = [ ( ' remote ' , casync . RemoteChunkReader ( self . store_fn ) , casync . build_chunk_dict ( target ) ) ]
stats = casync . extract ( target , sources , self . target_lo )
with open ( self . target_lo , ' rb ' ) as f :
self . assertEqual ( f . read ( len ( self . contents ) ) , self . contents )
self . assertLess ( stats [ ' remote ' ] , len ( self . contents ) )
if __name__ == " __main__ " :
unittest . main ( )