From 3fd428f99e72065c31bdcdbd1c8d75e4e1c1e36e Mon Sep 17 00:00:00 2001 From: Tomasz Iwanek Date: Fri, 19 Aug 2016 10:49:17 +0200 Subject: [PATCH] User 'app_fw' for smoke tests To verify, run: app_fw@localhost:~$ /usr/bin/wgt-backend-ut/smoke-test Fail expected: 'SmokeTest.RecoveryMode_Update' (on second run) This will be fixed separately. Tests: - SmokeTest.MountInstallationMode - SmokeTest.MountUpdateMode are expected to fail as well. Requires: - https://review.tizen.org/gerrit/#/c/84457/ Change-Id: Id08ad6bc3f294522e6c01d652194ebd248b7510b --- packaging/wgt-backend-tests.manifest | 4 +- packaging/wgt-backend.spec | 3 +- src/unit_tests/smoke_test.cc | 81 ++++++++++++----------- src/unit_tests/test_samples/smoke/DisablePkg.wgt | Bin 38015 -> 37645 bytes 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/packaging/wgt-backend-tests.manifest b/packaging/wgt-backend-tests.manifest index 9e728be..0d58b09 100644 --- a/packaging/wgt-backend-tests.manifest +++ b/packaging/wgt-backend-tests.manifest @@ -3,7 +3,7 @@ - - + + diff --git a/packaging/wgt-backend.spec b/packaging/wgt-backend.spec index 9d9f89a..9741293 100644 --- a/packaging/wgt-backend.spec +++ b/packaging/wgt-backend.spec @@ -53,7 +53,8 @@ mkdir -p %{buildroot}/etc/package-manager/backend ln -s %{_bindir}/wgt-backend %{buildroot}%{_sysconfdir}/package-manager/backend/wgt %post tests -/usr/sbin/setcap cap_dac_override=eip %{_bindir}/wgt-backend-ut/smoke-test +/usr/sbin/setcap cap_chown,cap_dac_override,cap_fowner=eip %{_bindir}/wgt-backend-ut/smoke-test +/usr/sbin/setcap cap_chown,cap_dac_override,cap_fowner=eip %{_bindir}/wgt-backend-ut/smoke-test-helper %files %manifest wgt-backend.manifest diff --git a/src/unit_tests/smoke_test.cc b/src/unit_tests/smoke_test.cc index 3e80354..c56ebbd 100644 --- a/src/unit_tests/smoke_test.cc +++ b/src/unit_tests/smoke_test.cc @@ -38,6 +38,10 @@ namespace ci = common_installer; namespace { +const uid_t kTestUserId = tzplatform_getuid(TZ_SYS_DEFAULT_USER); +const std::string kTestUserIdStr = + std::to_string(kTestUserId); + const bf::path kSmokePackagesDirectory = "/usr/share/wgt-backend-ut/test_samples/smoke/"; @@ -56,7 +60,7 @@ class ScopedTzipInterface { public: explicit ScopedTzipInterface(const std::string& pkgid) : pkg_path_(bf::path(ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER))) / pkgid), + kTestUserId)) / pkgid), interface_(ci::GetMountLocation(pkg_path_)) { interface_.MountZip(ci::GetZipPackageLocation(pkg_path_, pkgid)); } @@ -101,7 +105,7 @@ bool TouchFile(const bf::path& path) { void RemoveAllRecoveryFiles() { bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); if (!bf::exists(root_path)) return; for (auto& dir_entry : boost::make_iterator_range( @@ -117,7 +121,7 @@ void RemoveAllRecoveryFiles() { bf::path FindRecoveryFile() { bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); for (auto& dir_entry : boost::make_iterator_range( bf::directory_iterator(root_path), bf::directory_iterator())) { if (bf::is_regular_file(dir_entry)) { @@ -133,7 +137,7 @@ bool ValidateFileContentInPackage(const std::string& pkgid, const std::string& relative, const std::string& expected) { bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); bf::path file_path = root_path / pkgid / relative; if (!bf::exists(file_path)) { LOG(ERROR) << file_path << " doesn't exist"; @@ -156,7 +160,7 @@ bool ValidateFileContentInPackage(const std::string& pkgid, void ValidatePackageFS(const std::string& pkgid, const std::vector& appids) { bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); bf::path package_path = root_path / pkgid; bf::path data_path = package_path / "data"; bf::path shared_path = package_path / "shared"; @@ -169,7 +173,7 @@ void ValidatePackageFS(const std::string& pkgid, bf::path manifest_path = bf::path(getUserManifestPath( - tzplatform_getuid(TZ_SYS_DEFAULT_USER), false)) / (pkgid + ".xml"); + kTestUserId, false)) / (pkgid + ".xml"); ASSERT_TRUE(bf::exists(manifest_path)); for (auto& appid : appids) { @@ -195,13 +199,13 @@ void ValidatePackageFS(const std::string& pkgid, void PackageCheckCleanup(const std::string& pkgid, const std::vector&) { bf::path root_path = ci::GetRootAppPath( - false, tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + false, kTestUserId); bf::path package_path = root_path / pkgid; ASSERT_FALSE(bf::exists(package_path)); bf::path manifest_path = bf::path(getUserManifestPath( - tzplatform_getuid(TZ_SYS_DEFAULT_USER), false)) / (pkgid + ".xml"); + kTestUserId, false)) / (pkgid + ".xml"); ASSERT_FALSE(bf::exists(manifest_path)); // backups should not exist @@ -214,16 +218,16 @@ void PackageCheckCleanup(const std::string& pkgid, void ValidatePackage(const std::string& pkgid, const std::vector& appids) { ASSERT_TRUE(ci::QueryIsPackageInstalled( - pkgid, ci::GetRequestMode(tzplatform_getuid(TZ_SYS_DEFAULT_USER)), - tzplatform_getuid(TZ_SYS_DEFAULT_USER))); + pkgid, ci::GetRequestMode(kTestUserId), + kTestUserId)); ValidatePackageFS(pkgid, appids); } void CheckPackageNonExistance(const std::string& pkgid, const std::vector& appids) { ASSERT_FALSE(ci::QueryIsPackageInstalled( - pkgid, ci::GetRequestMode(tzplatform_getuid(TZ_SYS_DEFAULT_USER)), - tzplatform_getuid(TZ_SYS_DEFAULT_USER))); + pkgid, ci::GetRequestMode(kTestUserId), + kTestUserId)); PackageCheckCleanup(pkgid, appids); } @@ -281,7 +285,7 @@ ci::AppInstaller::Result CallBackend(int argc, ci::AppInstaller::Result Install(const bf::path& path, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-i", path.c_str()}; + const char* argv[] = {"", "-i", path.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } @@ -298,7 +302,7 @@ ci::AppInstaller::Result Update(const bf::path& path_old, ci::AppInstaller::Result MountInstall(const bf::path& path, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-w", path.c_str()}; + const char* argv[] = {"", "-w", path.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } @@ -315,7 +319,7 @@ ci::AppInstaller::Result MountUpdate(const bf::path& path_old, ci::AppInstaller::Result Uninstall(const std::string& pkgid, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-d", pkgid.c_str()}; + const char* argv[] = {"", "-d", pkgid.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } @@ -327,7 +331,8 @@ ci::AppInstaller::Result Reinstall(const bf::path& path, LOG(ERROR) << "Failed to install application. Cannot perform RDS"; return ci::AppInstaller::Result::UNKNOWN; } - const char* argv[] = {"", "-r", delta_dir.c_str()}; + const char* argv[] = {"", "-r", delta_dir.c_str(), "-u", + kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } @@ -343,28 +348,29 @@ ci::AppInstaller::Result DeltaInstall(const bf::path& path, ci::AppInstaller::Result Clear(const std::string& pkgid, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-c", pkgid.c_str()}; + const char* argv[] = {"", "-c", pkgid.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } ci::AppInstaller::Result EnablePackage(const std::string& pkgid, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-A", pkgid.c_str()}; + const char* argv[] = {"", "-A", pkgid.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } ci::AppInstaller::Result DisablePackage(const std::string& pkgid, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-D", pkgid.c_str()}; + const char* argv[] = {"", "-D", pkgid.c_str(), "-u", kTestUserIdStr.c_str()}; return CallBackend(SIZEOFARRAY(argv), argv, type, mode); } ci::AppInstaller::Result Recover(const bf::path& recovery_file, PackageType type, RequestResult mode = RequestResult::NORMAL) { - const char* argv[] = {"", "-b", recovery_file.c_str()}; + const char* argv[] = {"", "-b", recovery_file.c_str(), "-u", + kTestUserIdStr.c_str()}; TestPkgmgrInstaller pkgmgr_installer; std::unique_ptr query_interface = CreateQueryInterface(); @@ -387,7 +393,7 @@ class SmokeEnvironment : public testing::Environment { explicit SmokeEnvironment(const bf::path& home) : home_(home) { } void SetUp() override { - bf::path UserDBDir = bf::path(kUserDataBaseDir) / std::to_string(getuid()); + bf::path UserDBDir = bf::path(kUserDataBaseDir) / kTestUserIdStr; bf::path UserDBDirBackup = UserDBDir.string() + std::string(".bck"); bs::error_code error; @@ -420,7 +426,7 @@ class SmokeEnvironment : public testing::Environment { } } void TearDown() override { - bf::path UserDBDir = bf::path(kUserDataBaseDir) / std::to_string(getuid()); + bf::path UserDBDir = bf::path(kUserDataBaseDir) / kTestUserIdStr; bf::path UserDBDirBackup = UserDBDir.string() + std::string(".bck"); bs::error_code error; @@ -483,7 +489,7 @@ TEST_F(SmokeTest, RDSMode) { // Check delta modifications bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); ASSERT_FALSE(bf::exists(root_path / pkgid / "res" / "wgt" / "DELETED")); ASSERT_TRUE(bf::exists(root_path / pkgid / "res" / "wgt" / "ADDED")); ValidateFileContentInPackage(pkgid, "res/wgt/MODIFIED", "2\n"); @@ -500,8 +506,8 @@ TEST_F(SmokeTest, EnablePkg) { ci::AppInstaller::Result::OK); ASSERT_TRUE(ci::QueryIsPackageInstalled(pkgid, - ci::GetRequestMode(tzplatform_getuid(TZ_SYS_DEFAULT_USER)), - tzplatform_getuid(TZ_SYS_DEFAULT_USER))); + ci::GetRequestMode(kTestUserId), + kTestUserId)); } TEST_F(SmokeTest, DisablePkg) { @@ -513,8 +519,8 @@ TEST_F(SmokeTest, DisablePkg) { ASSERT_EQ(DisablePackage(pkgid, PackageType::WGT), ci::AppInstaller::Result::OK); ASSERT_FALSE(ci::QueryIsPackageInstalled(pkgid, - ci::GetRequestMode(tzplatform_getuid(TZ_SYS_DEFAULT_USER)), - tzplatform_getuid(TZ_SYS_DEFAULT_USER))); + ci::GetRequestMode(kTestUserId), + kTestUserId)); ValidatePackageFS(pkgid, {appid}); } @@ -525,7 +531,7 @@ TEST_F(SmokeTest, ClearMode) { ASSERT_EQ(Install(path, PackageType::WGT), ci::AppInstaller::Result::OK); bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); bs::error_code error; bf::create_directory(root_path / pkgid / "data" / "dir", error); ASSERT_FALSE(error); @@ -548,7 +554,7 @@ TEST_F(SmokeTest, DeltaMode) { // Check delta modifications bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); ASSERT_FALSE(bf::exists(root_path / pkgid / "res" / "wgt" / "DELETED")); ASSERT_TRUE(bf::exists(root_path / pkgid / "res" / "wgt" / "ADDED")); ASSERT_TRUE(bf::exists(root_path / pkgid / "res" / "wgt" / "css" / "style.css")); // NOLINT @@ -560,7 +566,7 @@ TEST_F(SmokeTest, DeltaMode) { TEST_F(SmokeTest, RecoveryMode_ForInstallation) { bf::path path = kSmokePackagesDirectory / "RecoveryMode_ForInstallation.wgt"; Subprocess backend_crash("/usr/bin/wgt-backend-ut/smoke-test-helper"); - backend_crash.Run("-i", path.string()); + backend_crash.Run("-i", path.string(), "-u", kTestUserIdStr.c_str()); ASSERT_NE(backend_crash.Wait(), 0); std::string pkgid = "smokeapp09"; @@ -576,11 +582,9 @@ TEST_F(SmokeTest, RecoveryMode_ForUpdate) { bf::path path_old = kSmokePackagesDirectory / "RecoveryMode_ForUpdate.wgt"; bf::path path_new = kSmokePackagesDirectory / "RecoveryMode_ForUpdate_2.wgt"; RemoveAllRecoveryFiles(); - Subprocess backend_test("/usr/bin/wgt-backend"); - backend_test.Run("-i", path_old.string()); - ASSERT_EQ(backend_test.Wait(), 0); + ASSERT_EQ(Install(path_old, PackageType::WGT), ci::AppInstaller::Result::OK); Subprocess backend_crash("/usr/bin/wgt-backend-ut/smoke-test-helper"); - backend_crash.Run("-i", path_new.string()); + backend_crash.Run("-i", path_new.string(), "-u", kTestUserIdStr.c_str()); ASSERT_NE(backend_crash.Wait(), 0); std::string pkgid = "smokeapp10"; @@ -671,7 +675,7 @@ TEST_F(SmokeTest, DeltaMode_Hybrid) { // Check delta modifications bf::path root_path = ci::GetRootAppPath(false, - tzplatform_getuid(TZ_SYS_DEFAULT_USER)); + kTestUserId); ASSERT_FALSE(bf::exists(root_path / pkgid / "res" / "wgt" / "DELETED")); ASSERT_TRUE(bf::exists(root_path / pkgid / "res" / "wgt" / "ADDED")); ASSERT_FALSE(bf::exists(root_path / pkgid / "lib" / "DELETED")); @@ -709,11 +713,8 @@ TEST_F(SmokeTest, MountUpdateMode) { int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); - const char* directory = getenv("HOME"); - if (!directory) { - LOG(ERROR) << "Cannot get $HOME value"; - return 1; - } + bf::path directory = + bf::path("/home") / tzplatform_getenv(TZ_SYS_DEFAULT_USER); testing::AddGlobalTestEnvironment( new common_installer::SmokeEnvironment(directory)); return RUN_ALL_TESTS(); diff --git a/src/unit_tests/test_samples/smoke/DisablePkg.wgt b/src/unit_tests/test_samples/smoke/DisablePkg.wgt index 3245443fdd54a4557dc6877b64e6c4032ef1b7c3..905c527d8af0e23de542a87b1b13a5b400c37d5e 100644 GIT binary patch delta 4860 zcmZu#WmFVgw;n=r=mtS*NC8n8kd8qekWjiofng{ShK`|AKxU+*rCUPjZfOAl8FDCT zBqikX#&_3yzxSTA&RYBI^_*wNkF%b&uM2Tw@^Q(v)bQ|W0YpSZ0C58vD0(D90Lkok zS`i`Dps5!=L>h~F(jANQlD@jx#Io&f&-O({YaM#`hn3WO_>W;3gZth3>&C^yMRDgJ zx-Hkhj�^PH;V>U&Q+{9c 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 -- 2.7.4