summaryrefslogtreecommitdiffstats
path: root/mmc_updater/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'mmc_updater/src/tests')
-rw-r--r--mmc_updater/src/tests/CMakeLists.txt51
-rw-r--r--mmc_updater/src/tests/TestFileUtils.cpp50
-rw-r--r--mmc_updater/src/tests/TestFileUtils.h10
-rw-r--r--mmc_updater/src/tests/TestUpdateScript.cpp48
-rw-r--r--mmc_updater/src/tests/TestUpdateScript.h9
-rw-r--r--mmc_updater/src/tests/TestUpdaterOptions.cpp68
-rw-r--r--mmc_updater/src/tests/TestUpdaterOptions.h8
-rw-r--r--mmc_updater/src/tests/TestUtils.h108
-rw-r--r--mmc_updater/src/tests/file_list.xml52
-rw-r--r--mmc_updater/src/tests/new_app.cpp8
-rw-r--r--mmc_updater/src/tests/old_app.cpp7
-rwxr-xr-xmmc_updater/src/tests/test-update.rb218
-rw-r--r--mmc_updater/src/tests/v2_file_list.xml67
13 files changed, 704 insertions, 0 deletions
diff --git a/mmc_updater/src/tests/CMakeLists.txt b/mmc_updater/src/tests/CMakeLists.txt
new file mode 100644
index 00000000..2af9b9c0
--- /dev/null
+++ b/mmc_updater/src/tests/CMakeLists.txt
@@ -0,0 +1,51 @@
+
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/..")
+
+if (APPLE)
+ set(HELPER_SHARED_SOURCES ../StlSymbolsLeopard.cpp)
+endif()
+
+# Create helper binaries for unit tests
+add_executable(oldapp
+ old_app.cpp
+ ${HELPER_SHARED_SOURCES}
+)
+add_executable(newapp
+ new_app.cpp
+ ${HELPER_SHARED_SOURCES}
+)
+
+# Install data files required by unit tests
+set(TEST_FILES
+ file_list.xml
+ v2_file_list.xml
+ test-update.rb
+)
+
+foreach(TEST_FILE ${TEST_FILES})
+ execute_process(
+ COMMAND
+ "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" "${CMAKE_CURRENT_BINARY_DIR}"
+ )
+endforeach()
+
+# Add unit test binaries
+macro(ADD_UPDATER_TEST CLASS)
+ set(TEST_TARGET updater_${CLASS})
+ add_executable(${TEST_TARGET} ${CLASS}.cpp)
+ target_link_libraries(${TEST_TARGET} updatershared)
+ add_test(${TEST_TARGET} ${TEST_TARGET})
+ if (APPLE)
+ set_target_properties(${TEST_TARGET} PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa")
+ endif()
+endmacro()
+
+add_updater_test(TestUpdateScript)
+add_updater_test(TestUpdaterOptions)
+add_updater_test(TestFileUtils)
+
+# Add updater that that performs a complete update install
+# and checks the result
+find_program(RUBY_BIN ruby)
+add_test(updater_TestUpdateInstall ${RUBY_BIN} test-update.rb)
+
diff --git a/mmc_updater/src/tests/TestFileUtils.cpp b/mmc_updater/src/tests/TestFileUtils.cpp
new file mode 100644
index 00000000..709acc5c
--- /dev/null
+++ b/mmc_updater/src/tests/TestFileUtils.cpp
@@ -0,0 +1,50 @@
+#include "TestFileUtils.h"
+
+#include "FileUtils.h"
+#include "TestUtils.h"
+
+void TestFileUtils::testDirName()
+{
+#ifdef PLATFORM_WINDOWS
+ std::string dirName = FileUtils::dirname("E:/Some Dir/App.exe");
+ TEST_COMPARE(dirName,"E:/Some Dir/");
+#endif
+}
+
+void TestFileUtils::testIsRelative()
+{
+#ifdef PLATFORM_WINDOWS
+ TEST_COMPARE(FileUtils::isRelative("temp"),true);
+ TEST_COMPARE(FileUtils::isRelative("D:/temp"),false);
+ TEST_COMPARE(FileUtils::isRelative("d:/temp"),false);
+#else
+ TEST_COMPARE(FileUtils::isRelative("/tmp"),false);
+ TEST_COMPARE(FileUtils::isRelative("tmp"),true);
+#endif
+}
+
+void TestFileUtils::testSymlinkFileExists()
+{
+#ifdef PLATFORM_UNIX
+ const char* linkName = "link-name";
+ FileUtils::removeFile(linkName);
+ FileUtils::createSymLink(linkName, "target-that-does-not-exist");
+ TEST_COMPARE(FileUtils::fileExists(linkName), true);
+#endif
+}
+
+void TestFileUtils::testStandardDirs()
+{
+ std::string tmpDir = FileUtils::tempPath();
+ TEST_COMPARE(FileUtils::fileExists(tmpDir.data()), true);
+}
+
+int main(int,char**)
+{
+ TestList<TestFileUtils> tests;
+ tests.addTest(&TestFileUtils::testDirName);
+ tests.addTest(&TestFileUtils::testIsRelative);
+ tests.addTest(&TestFileUtils::testSymlinkFileExists);
+ tests.addTest(&TestFileUtils::testStandardDirs);
+ return TestUtils::runTest(tests);
+}
diff --git a/mmc_updater/src/tests/TestFileUtils.h b/mmc_updater/src/tests/TestFileUtils.h
new file mode 100644
index 00000000..1a45164b
--- /dev/null
+++ b/mmc_updater/src/tests/TestFileUtils.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class TestFileUtils
+{
+ public:
+ void testDirName();
+ void testIsRelative();
+ void testSymlinkFileExists();
+ void testStandardDirs();
+};
diff --git a/mmc_updater/src/tests/TestUpdateScript.cpp b/mmc_updater/src/tests/TestUpdateScript.cpp
new file mode 100644
index 00000000..9e8c1392
--- /dev/null
+++ b/mmc_updater/src/tests/TestUpdateScript.cpp
@@ -0,0 +1,48 @@
+#include "TestUpdateScript.h"
+
+#include "TestUtils.h"
+#include "UpdateScript.h"
+
+#include <iostream>
+#include <algorithm>
+
+void TestUpdateScript::testV2Script()
+{
+ UpdateScript newFormat;
+ UpdateScript oldFormat;
+
+ newFormat.parse("file_list.xml");
+ oldFormat.parse("v2_file_list.xml");
+
+ TEST_COMPARE(newFormat.filesToInstall(),oldFormat.filesToInstall());
+ TEST_COMPARE(newFormat.filesToUninstall(),oldFormat.filesToUninstall());
+}
+
+void TestUpdateScript::testPermissions()
+{
+ UpdateScript script;
+ script.parse("file_list.xml");
+
+ for (std::vector<UpdateScriptFile>::const_iterator iter = script.filesToInstall().begin();
+ iter != script.filesToInstall().end();
+ iter++)
+ {
+ if (iter->isMainBinary)
+ {
+ TEST_COMPARE(iter->permissions,0755);
+ }
+ if (!iter->linkTarget.empty())
+ {
+ TEST_COMPARE(iter->permissions,0);
+ }
+ }
+}
+
+int main(int,char**)
+{
+ TestList<TestUpdateScript> tests;
+ tests.addTest(&TestUpdateScript::testV2Script);
+ tests.addTest(&TestUpdateScript::testPermissions);
+ return TestUtils::runTest(tests);
+}
+
diff --git a/mmc_updater/src/tests/TestUpdateScript.h b/mmc_updater/src/tests/TestUpdateScript.h
new file mode 100644
index 00000000..fd0994fe
--- /dev/null
+++ b/mmc_updater/src/tests/TestUpdateScript.h
@@ -0,0 +1,9 @@
+#pragma once
+
+class TestUpdateScript
+{
+ public:
+ void testV2Script();
+ void testPermissions();
+};
+
diff --git a/mmc_updater/src/tests/TestUpdaterOptions.cpp b/mmc_updater/src/tests/TestUpdaterOptions.cpp
new file mode 100644
index 00000000..a4cb7d33
--- /dev/null
+++ b/mmc_updater/src/tests/TestUpdaterOptions.cpp
@@ -0,0 +1,68 @@
+#include "TestUpdaterOptions.h"
+
+#include "FileUtils.h"
+#include "Platform.h"
+#include "TestUtils.h"
+#include "UpdaterOptions.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+void TestUpdaterOptions::testOldFormatArgs()
+{
+ const int argc = 6;
+ char* argv[argc];
+ argv[0] = strdup("updater");
+
+ std::string currentDir("CurrentDir=");
+ const char* appDir = 0;
+
+ // CurrentDir is the path to the directory containing the main
+ // Mendeley Desktop binary, on Linux and Mac this differs from
+ // the root of the install directory
+#ifdef PLATFORM_LINUX
+ appDir = "/tmp/path-to-app/lib/mendeleydesktop/libexec/";
+ FileUtils::mkpath(appDir);
+#elif defined(PLATFORM_MAC)
+ appDir = "/tmp/path-to-app/Contents/MacOS/";
+ FileUtils::mkpath(appDir);
+#elif defined(PLATFORM_WINDOWS)
+ appDir = "C:/path/to/app/";
+#endif
+ currentDir += appDir;
+
+ argv[1] = strdup(currentDir.c_str());
+ argv[2] = strdup("TempDir=/tmp/updater");
+ argv[3] = strdup("UpdateScriptFileName=/tmp/updater/file_list.xml");
+ argv[4] = strdup("AppFileName=/path/to/app/theapp");
+ argv[5] = strdup("PID=123456");
+
+ UpdaterOptions options;
+ options.parse(argc,argv);
+
+ TEST_COMPARE(options.mode,UpdateInstaller::Setup);
+#ifdef PLATFORM_LINUX
+ TEST_COMPARE(options.installDir,"/tmp/path-to-app");
+#elif defined(PLATFORM_MAC)
+ // /tmp is a symlink to /private/tmp on Mac
+ TEST_COMPARE(options.installDir,"/private/tmp/path-to-app");
+#else
+ TEST_COMPARE(options.installDir,"C:/path/to/app/");
+#endif
+ TEST_COMPARE(options.packageDir,"/tmp/updater");
+ TEST_COMPARE(options.scriptPath,"/tmp/updater/file_list.xml");
+ TEST_COMPARE(options.waitPid,123456);
+
+ for (int i=0; i < argc; i++)
+ {
+ free(argv[i]);
+ }
+}
+
+int main(int,char**)
+{
+ TestList<TestUpdaterOptions> tests;
+ tests.addTest(&TestUpdaterOptions::testOldFormatArgs);
+ return TestUtils::runTest(tests);
+}
+
diff --git a/mmc_updater/src/tests/TestUpdaterOptions.h b/mmc_updater/src/tests/TestUpdaterOptions.h
new file mode 100644
index 00000000..5ed102c1
--- /dev/null
+++ b/mmc_updater/src/tests/TestUpdaterOptions.h
@@ -0,0 +1,8 @@
+#pragma once
+
+class TestUpdaterOptions
+{
+ public:
+ void testOldFormatArgs();
+};
+
diff --git a/mmc_updater/src/tests/TestUtils.h b/mmc_updater/src/tests/TestUtils.h
new file mode 100644
index 00000000..68d97da5
--- /dev/null
+++ b/mmc_updater/src/tests/TestUtils.h
@@ -0,0 +1,108 @@
+#pragma once
+
+#include <iostream>
+#include <functional>
+#include <string>
+#include <vector>
+
+template <class T>
+class TestList
+{
+ public:
+ void addTest(void (T::*test)())
+ {
+ m_tests.push_back(std::mem_fun(test));
+ }
+
+ int size() const
+ {
+ return m_tests.size();
+ }
+
+ void runTest(T* testInstance, int i)
+ {
+ m_tests.at(i)(testInstance);
+ }
+
+ private:
+ std::vector<std::mem_fun_t<void,T> > m_tests;
+};
+
+class TestUtils
+{
+ public:
+ template <class X, class Y>
+ static void compare(const X& x, const Y& y, const char* xString, const char* yString)
+ {
+ if (x != y)
+ {
+ throw "Actual and expected values differ. "
+ "Actual: " + toString(x,xString) +
+ " Expected: " + toString(y,yString);
+ }
+ }
+
+ template <typename T>
+ static std::string toString(T value, const char* context)
+ {
+ return "Unprintable: " + std::string(context);
+ }
+
+ template <class T>
+ static int runTest(class TestList<T>& tests) throw ()
+ {
+ std::string errorText;
+ try
+ {
+ T testInstance;
+ for (int i=0; i < tests.size(); i++)
+ {
+ tests.runTest(&testInstance,i);
+ }
+ }
+ catch (const std::exception& ex)
+ {
+ errorText = ex.what();
+ }
+ catch (const std::string& error)
+ {
+ errorText = error;
+ }
+ catch (...)
+ {
+ errorText = "Unknown exception";
+ }
+
+ if (errorText.empty())
+ {
+ std::cout << "Test passed" << std::endl;
+ return 0;
+ }
+ else
+ {
+ std::cout << "Test failed: " << errorText << std::endl;
+ return 1;
+ }
+ }
+};
+
+template <>
+inline std::string TestUtils::toString(const std::string& value, const char*)
+{
+ return value;
+}
+template <>
+inline std::string TestUtils::toString(std::string value, const char*)
+{
+ return value;
+}
+template <>
+inline std::string TestUtils::toString(const char* value, const char*)
+{
+ return value;
+}
+
+#define TEST_COMPARE(x,y) \
+ TestUtils::compare(x,y,#x,#y);
+
+
diff --git a/mmc_updater/src/tests/file_list.xml b/mmc_updater/src/tests/file_list.xml
new file mode 100644
index 00000000..dff4b54f
--- /dev/null
+++ b/mmc_updater/src/tests/file_list.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<update version="3">
+ <targetVersion>2.0</targetVersion>
+ <platform>Test</platform>
+ <dependencies>
+ <!-- The new updater is standalone and has no dependencies,
+ except for standard system libraries.
+ !-->
+ </dependencies>
+ <packages>
+ <package>
+ <name>app-pkg</name>
+ <hash>$APP_PACKAGE_HASH</hash>
+ <size>$APP_PACKAGE_SIZE</size>
+ <source>http://some/dummy/URL</source>
+ </package>
+ </packages>
+ <install>
+ <file>
+ <name>$APP_FILENAME</name>
+ <hash>$UPDATED_APP_HASH</hash>
+ <size>$UPDATED_APP_SIZE</size>
+ <permissions>0755</permissions>
+ <package>app-pkg</package>
+ <is-main-binary>true</is-main-binary>
+ </file>
+ <file>
+ <name>$UPDATER_FILENAME</name>
+ <hash>$UPDATER_HASH</hash>
+ <size>$UPDATER_SIZE</size>
+ <permissions>0755</permissions>
+ </file>
+ <!-- Test symlink !-->
+ <file>
+ <name>test-dir/app-symlink</name>
+ <target>../app</target>
+ </file>
+ <!-- Test file in new directory !-->
+ <file>
+ <name>new-dir/new-dir2/new-file.txt</name>
+ <hash>$TEST_FILENAME</hash>
+ <size>$TEST_SIZE</size>
+ <package>app-pkg</package>
+ <permissions>0644</permissions>
+ </file>
+ </install>
+ <uninstall>
+ <!-- TODO - List some files to uninstall here !-->
+ <file>file-to-uninstall.txt</file>
+ <file>symlink-to-file-to-uninstall.txt</file>
+ </uninstall>
+</update>
diff --git a/mmc_updater/src/tests/new_app.cpp b/mmc_updater/src/tests/new_app.cpp
new file mode 100644
index 00000000..7fad1380
--- /dev/null
+++ b/mmc_updater/src/tests/new_app.cpp
@@ -0,0 +1,8 @@
+#include <iostream>
+
+int main(int,char**)
+{
+ std::cout << "new app starting" << std::endl;
+ return 0;
+}
+
diff --git a/mmc_updater/src/tests/old_app.cpp b/mmc_updater/src/tests/old_app.cpp
new file mode 100644
index 00000000..476a3405
--- /dev/null
+++ b/mmc_updater/src/tests/old_app.cpp
@@ -0,0 +1,7 @@
+#include <iostream>
+
+int main(int,char**)
+{
+ std::cout << "old app starting" << std::endl;
+ return 0;
+}
diff --git a/mmc_updater/src/tests/test-update.rb b/mmc_updater/src/tests/test-update.rb
new file mode 100755
index 00000000..82965cf4
--- /dev/null
+++ b/mmc_updater/src/tests/test-update.rb
@@ -0,0 +1,218 @@
+#!/usr/bin/ruby
+
+require 'fileutils.rb'
+require 'find'
+require 'rbconfig'
+require 'optparse'
+
+# Install directory - this contains a space to check
+# for correct escaping of paths when passing comamnd
+# line arguments under Windows
+INSTALL_DIR = File.expand_path("install dir/")
+PACKAGE_DIR = File.expand_path("package-dir/")
+PACKAGE_SRC_DIR = File.expand_path("package-src-dir/")
+IS_WINDOWS = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+
+if IS_WINDOWS
+ OLDAPP_NAME = "oldapp.exe"
+ NEWAPP_NAME = "newapp.exe"
+ APP_NAME = "app.exe"
+ UPDATER_NAME = "updater.exe"
+ ZIP_TOOL = File.expand_path("../zip-tool.exe")
+else
+ OLDAPP_NAME = "oldapp"
+ NEWAPP_NAME = "newapp"
+ APP_NAME = "app"
+ UPDATER_NAME = "updater"
+ ZIP_TOOL = File.expand_path("../zip-tool")
+end
+
+file_list_vars = {
+ "APP_FILENAME" => APP_NAME,
+ "UPDATER_FILENAME" => UPDATER_NAME
+}
+
+def replace_vars(src_file,dest_file,vars)
+ content = File.read(src_file)
+ vars.each do |key,value|
+ content.gsub! "$#{key}",value
+ end
+ File.open(dest_file,'w') do |file|
+ file.print content
+ end
+end
+
+# Returns true if |src_file| and |dest_file| have the same contents, type
+# and permissions or false otherwise
+def compare_files(src_file, dest_file)
+ if File.ftype(src_file) != File.ftype(dest_file)
+ $stderr.puts "Type of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file)
+ $stderr.puts "Contents of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ src_stat = File.stat(src_file)
+ dest_stat = File.stat(dest_file)
+
+ if src_stat.mode != dest_stat.mode
+ $stderr.puts "Permissions of #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ return true
+end
+
+# Compares the contents of two directories and returns a map of (file path => change type)
+# for files and directories which differ between the two
+def compare_dirs(src_dir, dest_dir)
+ src_dir += '/' if !src_dir.end_with?('/')
+ dest_dir += '/' if !dest_dir.end_with?('/')
+
+ src_file_map = {}
+ Find.find(src_dir) do |src_file|
+ src_file = src_file[src_dir.length..-1]
+ src_file_map[src_file] = nil
+ end
+
+ change_map = {}
+ Find.find(dest_dir) do |dest_file|
+ dest_file = dest_file[dest_dir.length..-1]
+
+ if !src_file_map.include?(dest_file)
+ change_map[dest_file] = :deleted
+ elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}")
+ change_map[dest_file] = :updated
+ end
+
+ src_file_map.delete(dest_file)
+ end
+
+ src_file_map.each do |file|
+ change_map[file] = :added
+ end
+
+ return change_map
+end
+
+def create_test_file(name, content)
+ File.open(name, 'w') do |file|
+ file.puts content
+ end
+ return name
+end
+
+force_elevation = false
+run_in_debugger = false
+
+OptionParser.new do |parser|
+ parser.on("-f","--force-elevated","Force the updater to elevate itself") do
+ force_elevation = true
+ end
+ parser.on("-d","--debug","Run the updater under GDB") do
+ run_in_debugger = true
+ end
+end.parse!
+
+# copy 'src' to 'dest', preserving the attributes
+# of 'src'
+def copy_file(src, dest)
+ FileUtils.cp src, dest, :preserve => true
+end
+
+# Remove the install and package dirs if they
+# already exist
+FileUtils.rm_rf(INSTALL_DIR)
+FileUtils.rm_rf(PACKAGE_DIR)
+FileUtils.rm_rf(PACKAGE_SRC_DIR)
+
+# Create the install directory with the old app
+Dir.mkdir(INSTALL_DIR)
+copy_file OLDAPP_NAME, "#{INSTALL_DIR}/#{APP_NAME}"
+
+# Create a dummy file to uninstall
+uninstall_test_file = create_test_file("#{INSTALL_DIR}/file-to-uninstall.txt", "this file should be removed after the update")
+uninstall_test_symlink = if not IS_WINDOWS
+ FileUtils.ln_s("#{INSTALL_DIR}/file-to-uninstall.txt", "#{INSTALL_DIR}/symlink-to-file-to-uninstall.txt")
+else
+ create_test_file("#{INSTALL_DIR}/symlink-to-file-to-uninstall.txt", "dummy file. this is a symlink on Unix")
+end
+
+# Populate package source dir with files to install
+Dir.mkdir(PACKAGE_SRC_DIR)
+nested_dir_path = "#{PACKAGE_SRC_DIR}/new-dir/new-dir2"
+FileUtils.mkdir_p(nested_dir_path)
+FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/new-dir"
+FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/new-dir/new-dir2"
+nested_dir_test_file = "#{nested_dir_path}/new-file.txt"
+File.open(nested_dir_test_file,'w') do |file|
+ file.puts "this is a new file in a new nested dir"
+end
+FileUtils::chmod 0644, nested_dir_test_file
+copy_file NEWAPP_NAME, "#{PACKAGE_SRC_DIR}/#{APP_NAME}"
+FileUtils::chmod 0755, "#{PACKAGE_SRC_DIR}/#{APP_NAME}"
+
+# Create .zip packages from source files
+Dir.mkdir(PACKAGE_DIR)
+Dir.chdir(PACKAGE_SRC_DIR) do
+ if !system("#{ZIP_TOOL} #{PACKAGE_DIR}/app-pkg.zip .")
+ raise "Unable to create update package"
+ end
+end
+
+# Copy the install script and updater to the target
+# directory
+replace_vars("file_list.xml","#{PACKAGE_DIR}/file_list.xml",file_list_vars)
+copy_file "../#{UPDATER_NAME}", "#{PACKAGE_DIR}/#{UPDATER_NAME}"
+
+# Run the updater using the new syntax
+#
+# Run the application from the install directory to
+# make sure that it looks in the correct directory for
+# the file_list.xml file and packages
+#
+install_path = File.expand_path(INSTALL_DIR)
+Dir.chdir(INSTALL_DIR) do
+ flags = "--force-elevated" if force_elevation
+ debug_flags = "gdb --args" if run_in_debugger
+ cmd = "#{debug_flags} #{PACKAGE_DIR}/#{UPDATER_NAME} #{flags} --install-dir \"#{install_path}\" --package-dir \"#{PACKAGE_DIR}\" --script file_list.xml --auto-close"
+ puts "Running '#{cmd}'"
+ system(cmd)
+end
+
+# TODO - Correctly wait until updater has finished
+sleep(1)
+
+# Check that the app was updated
+app_path = "#{INSTALL_DIR}/#{APP_NAME}"
+output = `"#{app_path}"`
+if (output.strip != "new app starting")
+ throw "Updated app produced unexpected output: #{output}"
+end
+
+# Check that the packaged dir and install dir match
+dir_diff = compare_dirs(PACKAGE_SRC_DIR, INSTALL_DIR)
+ignored_files = ["test-dir", "test-dir/app-symlink", UPDATER_NAME]
+have_unexpected_change = false
+dir_diff.each do |path, change_type|
+ if !ignored_files.include?(path)
+ case change_type
+ when :added
+ $stderr.puts "File #{path} was not installed"
+ when :changed
+ $stderr.puts "File #{path} differs between install and package dir"
+ when :deleted
+ $stderr.puts "File #{path} was not uninstalled"
+ end
+ have_unexpected_change = true
+ end
+end
+
+if have_unexpected_change
+ throw "Unexpected differences between packaging and update dir"
+end
+
+puts "Test passed"
diff --git a/mmc_updater/src/tests/v2_file_list.xml b/mmc_updater/src/tests/v2_file_list.xml
new file mode 100644
index 00000000..202e5bbe
--- /dev/null
+++ b/mmc_updater/src/tests/v2_file_list.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+
+<!-- The v2-compatible attribute lets the update script parser
+ know that it is dealing with a script structured for backwards
+ compatibility with the MD <= 1.0 updater.
+!-->
+<update version="3" v2-compatible="true">
+ <targetVersion>2.0</targetVersion>
+ <platform>Test</platform>
+ <dependencies>
+ <!-- The new updater is standalone and has no dependencies,
+ except for standard system libraries and itself.
+ !-->
+ </dependencies>
+ <packages>
+ <package>
+ <name>app-pkg</name>
+ <hash>$APP_PACKAGE_HASH</hash>
+ <size>$APP_PACKAGE_SIZE</size>
+ <source>http://some/dummy/URL</source>
+ </package>
+ </packages>
+
+ <!-- For compatibility with the update download in MD <= 1.0,
+ an <install> section lists the packages to download and
+ the real list of files to install is in the <install-v3>
+ section. !-->
+ <install>
+ <!-- A duplicate of the <packages> section should appear here,
+ except that each package is listed using the same structure
+ as files in the install-v3/files section.
+ !-->
+ </install>
+ <install-v3>
+ <file>
+ <name>$APP_FILENAME</name>
+ <hash>$UPDATED_APP_HASH</hash>
+ <size>$UPDATED_APP_SIZE</size>
+ <permissions>0755</permissions>
+ <package>app-pkg</package>
+ <is-main-binary>true</is-main-binary>
+ </file>
+ <file>
+ <name>$UPDATER_FILENAME</name>
+ <hash>$UPDATER_HASH</hash>
+ <size>$UPDATER_SIZE</size>
+ <permissions>0755</permissions>
+ </file>
+ <!-- Test symlink !-->
+ <file>
+ <name>test-dir/app-symlink</name>
+ <target>../app</target>
+ </file>
+ <file>
+ <name>new-dir/new-dir2/new-file.txt</name>
+ <hash>$TEST_FILENAME</hash>
+ <size>$TEST_SIZE</size>
+ <package>app-pkg</package>
+ <permissions>0644</permissions>
+ </file>
+ </install-v3>
+ <uninstall>
+ <!-- TODO - List some files to uninstall here !-->
+ <file>file-to-uninstall.txt</file>
+ <file>symlink-to-file-to-uninstall.txt</file>
+ </uninstall>
+</update>