Merge "updater_sample: update tools"

This commit is contained in:
Zhomart Mukhamejanov
2018-05-09 01:04:59 +00:00
committed by Gerrit Code Review
7 changed files with 74 additions and 93 deletions
+4 -3
View File
@@ -69,14 +69,15 @@ purpose only.
update zip file update zip file
- [x] Add `UpdateConfig` for working with json config files - [x] Add `UpdateConfig` for working with json config files
- [x] Add applying non-streaming update - [x] Add applying non-streaming update
- [ ] Prepare streaming update (partially downloading package) - [x] Prepare streaming update (partially downloading package)
- [ ] Add applying streaming update - [x] Add applying streaming update
- [x] Add stop/reset the update
- [ ] Add tests for `MainActivity` - [ ] Add tests for `MainActivity`
- [ ] Add stop/reset the update
- [ ] Verify system partition checksum for package - [ ] Verify system partition checksum for package
- [ ] HAL compatibility check - [ ] HAL compatibility check
- [ ] Change partition demo - [ ] Change partition demo
- [ ] Add non-A/B updates demo - [ ] Add non-A/B updates demo
- [ ] Add docs for passing HTTP headers to `UpdateEngine#applyPayload`
## Running tests ## Running tests
Binary file not shown.
@@ -3,30 +3,35 @@
"ab_install_type": "STREAMING", "ab_install_type": "STREAMING",
"ab_streaming_metadata": { "ab_streaming_metadata": {
"property_files": [ "property_files": [
{
"filename": "payload_metadata.bin",
"offset": 41,
"size": 827
},
{ {
"filename": "payload.bin", "filename": "payload.bin",
"offset": 41, "offset": 41,
"size": 7 "size": 1392
}, },
{ {
"filename": "payload_properties.txt", "filename": "payload_properties.txt",
"offset": 100, "offset": 1485,
"size": 18 "size": 147
}, },
{ {
"filename": "care_map.txt", "filename": "care_map.txt",
"offset": 160, "offset": 1674,
"size": 8 "size": 12
}, },
{ {
"filename": "compatibility.zip", "filename": "compatibility.zip",
"offset": 215, "offset": 1733,
"size": 13 "size": 17
}, },
{ {
"filename": "metadata", "filename": "metadata",
"offset": 287, "offset": 1809,
"size": 8 "size": 29
} }
] ]
}, },
@@ -70,11 +70,11 @@ public class FileDownloaderTest {
.toFile(); .toFile();
Files.deleteIfExists(outFile.toPath()); Files.deleteIfExists(outFile.toPath());
// download a chunk of ota.zip // download a chunk of ota.zip
FileDownloader downloader = new FileDownloader(url, 160, 8, outFile); FileDownloader downloader = new FileDownloader(url, 1674, 12, outFile);
downloader.download(); downloader.download();
String downloadedContent = String.join("\n", Files.readAllLines(outFile.toPath())); String downloadedContent = String.join("\n", Files.readAllLines(outFile.toPath()));
// archive contains text files with uppercase filenames // archive contains text files with uppercase filenames
assertEquals("CARE_MAP", downloadedContent); assertEquals("CARE_MAP-TXT", downloadedContent);
} }
} }
@@ -17,8 +17,6 @@
package com.example.android.systemupdatersample.util; package com.example.android.systemupdatersample.util;
import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME; import static com.example.android.systemupdatersample.util.PackageFiles.PAYLOAD_BINARY_FILE_NAME;
import static com.example.android.systemupdatersample.util.PackageFiles
.PAYLOAD_PROPERTIES_FILE_NAME;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -29,6 +27,7 @@ import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import com.example.android.systemupdatersample.PayloadSpec; import com.example.android.systemupdatersample.PayloadSpec;
import com.example.android.systemupdatersample.tests.R;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.Files; import com.google.common.io.Files;
@@ -39,12 +38,8 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.file.Paths;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/** /**
* Tests if PayloadSpecs parses update package zip file correctly. * Tests if PayloadSpecs parses update package zip file correctly.
@@ -54,12 +49,11 @@ import java.util.zip.ZipOutputStream;
public class PayloadSpecsTest { public class PayloadSpecsTest {
private static final String PROPERTIES_CONTENTS = "k1=val1\nkey2=val2"; private static final String PROPERTIES_CONTENTS = "k1=val1\nkey2=val2";
private static final String PAYLOAD_CONTENTS = "hello\nworld";
private static final int PAYLOAD_SIZE = PAYLOAD_CONTENTS.length();
private File mTestDir; private File mTestDir;
private Context mTargetContext; private Context mTargetContext;
private Context mTestContext;
@Rule @Rule
public final ExpectedException thrown = ExpectedException.none(); public final ExpectedException thrown = ExpectedException.none();
@@ -67,21 +61,30 @@ public class PayloadSpecsTest {
@Before @Before
public void setUp() { public void setUp() {
mTargetContext = InstrumentationRegistry.getTargetContext(); mTargetContext = InstrumentationRegistry.getTargetContext();
mTestContext = InstrumentationRegistry.getContext();
mTestDir = mTargetContext.getFilesDir(); mTestDir = mTargetContext.getFilesDir();
} }
@Test @Test
public void forNonStreaming_works() throws Exception { public void forNonStreaming_works() throws Exception {
File packageFile = createMockZipFile(); // Prepare the target file
File packageFile = Paths
.get(mTargetContext.getCacheDir().getAbsolutePath(), "ota.zip")
.toFile();
java.nio.file.Files.deleteIfExists(packageFile.toPath());
java.nio.file.Files.copy(mTestContext.getResources().openRawResource(R.raw.ota_002_package),
packageFile.toPath());
PayloadSpec spec = PayloadSpecs.forNonStreaming(packageFile); PayloadSpec spec = PayloadSpecs.forNonStreaming(packageFile);
assertEquals("correct url", "file://" + packageFile.getAbsolutePath(), spec.getUrl()); assertEquals("correct url", "file://" + packageFile.getAbsolutePath(), spec.getUrl());
assertEquals("correct payload offset", assertEquals("correct payload offset",
30 + PAYLOAD_BINARY_FILE_NAME.length(), spec.getOffset()); 30 + PAYLOAD_BINARY_FILE_NAME.length(), spec.getOffset());
assertEquals("correct payload size", PAYLOAD_SIZE, spec.getSize()); assertEquals("correct payload size", 1392, spec.getSize());
assertArrayEquals("correct properties", assertEquals(4, spec.getProperties().size());
new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0])); assertEquals(
"FILE_HASH=sEAK/NMbU7GGe01xt55FsPafIPk8IYyBOAd6SiDpiMs=",
spec.getProperties().get(0));
} }
@Test @Test
@@ -105,33 +108,6 @@ public class PayloadSpecsTest {
new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0])); new String[]{"k1=val1", "key2=val2"}, spec.getProperties().toArray(new String[0]));
} }
/**
* Creates package zip file that contains payload.bin and payload_properties.txt
*/
private File createMockZipFile() throws IOException {
File testFile = new File(mTestDir, "test.zip");
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(testFile))) {
// Add payload.bin entry.
ZipEntry entry = new ZipEntry(PAYLOAD_BINARY_FILE_NAME);
entry.setMethod(ZipEntry.STORED);
entry.setCompressedSize(PAYLOAD_SIZE);
entry.setSize(PAYLOAD_SIZE);
CRC32 crc = new CRC32();
crc.update(PAYLOAD_CONTENTS.getBytes(StandardCharsets.UTF_8));
entry.setCrc(crc.getValue());
zos.putNextEntry(entry);
zos.write(PAYLOAD_CONTENTS.getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
// Add payload properties entry.
ZipEntry propertiesEntry = new ZipEntry(PAYLOAD_PROPERTIES_FILE_NAME);
zos.putNextEntry(propertiesEntry);
zos.write(PROPERTIES_CONTENTS.getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
return testFile;
}
private File createMockPropertiesFile() throws IOException { private File createMockPropertiesFile() throws IOException {
File propertiesFile = new File(mTestDir, PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME); File propertiesFile = new File(mTestDir, PackageFiles.PAYLOAD_PROPERTIES_FILE_NAME);
Files.asCharSink(propertiesFile, Charsets.UTF_8).write(PROPERTIES_CONTENTS); Files.asCharSink(propertiesFile, Charsets.UTF_8).write(PROPERTIES_CONTENTS);
+22 -29
View File
@@ -17,11 +17,13 @@
""" """
Given a OTA package file, produces update config JSON file. Given a OTA package file, produces update config JSON file.
Example: tools/gen_update_config.py \\ Example:
--ab_install_type=STREAMING \\ $ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\
ota-build-001.zip \\ bootable/recovery/updater_sample/tools/gen_update_config.py \\
my-config-001.json \\ --ab_install_type=STREAMING \\
http://foo.bar/ota-builds/ota-build-001.zip ota-build-001.zip \\
my-config-001.json \\
http://foo.bar/ota-builds/ota-build-001.zip
""" """
import argparse import argparse
@@ -30,6 +32,8 @@ import os.path
import sys import sys
import zipfile import zipfile
import ota_from_target_files # pylint: disable=import-error
class GenUpdateConfig(object): class GenUpdateConfig(object):
""" """
@@ -41,7 +45,6 @@ class GenUpdateConfig(object):
AB_INSTALL_TYPE_STREAMING = 'STREAMING' AB_INSTALL_TYPE_STREAMING = 'STREAMING'
AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING' AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING'
METADATA_NAME = 'META-INF/com/android/metadata'
def __init__(self, package, url, ab_install_type): def __init__(self, package, url, ab_install_type):
self.package = package self.package = package
@@ -82,37 +85,27 @@ class GenUpdateConfig(object):
def _gen_ab_streaming_metadata(self): def _gen_ab_streaming_metadata(self):
"""Builds metadata for files required for streaming update.""" """Builds metadata for files required for streaming update."""
with zipfile.ZipFile(self.package, 'r') as package_zip: with zipfile.ZipFile(self.package, 'r') as package_zip:
property_files = self._get_property_files(package_zip)
metadata = { metadata = {
'property_files': property_files 'property_files': self._get_property_files(package_zip)
} }
return metadata return metadata
def _get_property_files(self, zip_file): @staticmethod
def _get_property_files(package_zip):
"""Constructs the property-files list for A/B streaming metadata.""" """Constructs the property-files list for A/B streaming metadata."""
def compute_entry_offset_size(name): ab_ota = ota_from_target_files.AbOtaPropertyFiles()
"""Computes the zip entry offset and size.""" property_str = ab_ota.GetPropertyFilesString(package_zip, False)
info = zip_file.getinfo(name)
offset = info.header_offset + len(info.FileHeader())
size = info.file_size
return {
'filename': os.path.basename(name),
'offset': offset,
'size': size,
}
property_files = [] property_files = []
for entry in self.streaming_required: for file in property_str.split(','):
property_files.append(compute_entry_offset_size(entry)) filename, offset, size = file.split(':')
for entry in self.streaming_optional: inner_file = {
if entry in zip_file.namelist(): 'filename': filename,
property_files.append(compute_entry_offset_size(entry)) 'offset': int(offset),
'size': int(size)
# 'META-INF/com/android/metadata' is required }
property_files.append(compute_entry_offset_size(GenUpdateConfig.METADATA_NAME)) property_files.append(inner_file)
return property_files return property_files
@@ -15,7 +15,11 @@
# limitations under the License. # limitations under the License.
""" """
Tests gen_update_config.py Tests gen_update_config.py.
Example:
$ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\
python3 -m unittest test_gen_update_config
""" """
import os.path import os.path
@@ -29,15 +33,21 @@ class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstrin
"""tests if streaming property files' offset and size are generated properly""" """tests if streaming property files' offset and size are generated properly"""
config, package = self._generate_config() config, package = self._generate_config()
property_files = config['ab_streaming_metadata']['property_files'] property_files = config['ab_streaming_metadata']['property_files']
self.assertEqual(len(property_files), 5) self.assertEqual(len(property_files), 6)
with open(package, 'rb') as pkg_file: with open(package, 'rb') as pkg_file:
for prop in property_files: for prop in property_files:
filename, offset, size = prop['filename'], prop['offset'], prop['size'] filename, offset, size = prop['filename'], prop['offset'], prop['size']
pkg_file.seek(offset) pkg_file.seek(offset)
data = pkg_file.read(size).decode('ascii') raw_data = pkg_file.read(size)
# data in the archive are just uppercase filenames without extension if filename in ['payload.bin', 'payload_metadata.bin']:
expected_data = filename.split('.')[0].upper() pass
self.assertEqual(data, expected_data) elif filename == 'payload_properties.txt':
pass
elif filename == 'metadata':
self.assertEqual(raw_data.decode('ascii'), 'META-INF/COM/ANDROID/METADATA')
else:
expected_data = filename.replace('.', '-').upper()
self.assertEqual(raw_data.decode('ascii'), expected_data)
@staticmethod @staticmethod
def _generate_config(): def _generate_config():
@@ -49,7 +59,3 @@ class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstrin
GenUpdateConfig.AB_INSTALL_TYPE_STREAMING) GenUpdateConfig.AB_INSTALL_TYPE_STREAMING)
gen.run() gen.run()
return gen.config, ota_package return gen.config, ota_package
if __name__ == '__main__':
unittest.main()