Merge "updater_sample: update tools"
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-40
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+16
-10
@@ -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()
|
|
||||||
Reference in New Issue
Block a user