/** * This file Copyright (c) 2003-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.module; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; import static org.easymock.classextension.EasyMock.createStrictMock; import static org.easymock.classextension.EasyMock.replay; import static org.easymock.classextension.EasyMock.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.NodeData; import info.magnolia.context.MgnlContext; import info.magnolia.context.SystemContext; import info.magnolia.module.delta.AbstractTask; import info.magnolia.module.delta.Condition; import info.magnolia.module.delta.Delta; import info.magnolia.module.delta.DeltaBuilder; import info.magnolia.module.delta.Task; import info.magnolia.module.delta.TaskExecutionException; import info.magnolia.module.delta.WarnTask; import info.magnolia.module.model.ModuleDefinition; import info.magnolia.module.model.Version; import info.magnolia.module.model.reader.ModuleDefinitionReader; import info.magnolia.repository.DefaultRepositoryManager; import info.magnolia.repository.RepositoryManager; import info.magnolia.test.ComponentsTestUtil; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * @version $Id$ */ public class ModuleManagerImplTest { private ModuleRegistry moduleRegistry; @Before public void setUp() throws Exception { moduleRegistry = new ModuleRegistryImpl(); ComponentsTestUtil.setInstance(ModuleRegistry.class, moduleRegistry); ComponentsTestUtil.setInstance(SystemContext.class, createStrictMock(SystemContext.class)); ComponentsTestUtil.setInstance(RepositoryManager.class, new DefaultRepositoryManager()); // shunt log4j org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.OFF); } @After public void tearDown() throws Exception { ComponentsTestUtil.clear(); MgnlContext.setInstance(null); } @Test public void testCheckModuleAndDeltasToStringForUpdate() { final ModuleDefinition mod = new ModuleDefinition("foo", Version.parseVersion("2.3.4"), null, null); final Delta d1 = DeltaBuilder.update("1.1", "New version").addTask(new WarnTask("t1", "test 1")).addTask(new WarnTask("t2", "test 2")); final Delta d2 = DeltaBuilder.update("2.0", "New version 2").addTask(new WarnTask("t3", "test 3")).addTask(new WarnTask("t3", "test 4")); ModuleManager.ModuleAndDeltas mad = new ModuleManager.ModuleAndDeltas(mod, Version.parseVersion("1.0"), Arrays.asList(d1, d2)); assertEquals("ModuleAndDeltas for foo: current version is 1.0.0, updating to 2.3.4 with 2 deltas.", mad.toString()); } @Test public void testCheckModuleAndDeltasToStringForInstall() { final ModuleDefinition mod = new ModuleDefinition("foo", Version.parseVersion("2.3.4"), null, null); final Delta d1 = DeltaBuilder.update("1.1", "New version").addTask(new WarnTask("t1", "test 1")).addTask(new WarnTask("t2", "test 2")); ModuleManager.ModuleAndDeltas mad = new ModuleManager.ModuleAndDeltas(mod, null, Arrays.asList(d1)); assertEquals("ModuleAndDeltas for foo: installing version 2.3.4 with 1 deltas.", mad.toString()); } // TODO : assert saves after each module? // TODO : assert rollbs back with TaskExecutionException /** * TODO : should check that d1 is actually called before d2 * * TODO : since ContentRepository.getAllRepositoryNames() returns an empty iterator, we don't actually check the save operations */ @Test public void testUpdateAppliesSuppliedDeltasAndTasks() throws Exception { final String newVersion = "2.3.4"; final InstallContextImpl ctx = createStrictMock(InstallContextImpl.class); final ModuleDefinition mod = new ModuleDefinition("foo", Version.parseVersion(newVersion), null, null); final Content allModulesNode = createStrictMock(Content.class); final Content moduleNode = createStrictMock(Content.class); final NodeData versionProp = createStrictMock(NodeData.class); final Delta d1 = createStrictMock(Delta.class); final Delta d2 = createStrictMock(Delta.class); final Task t1 = createStrictMock(Task.class); final Task t2 = createStrictMock(Task.class); final Task t3 = createStrictMock(Task.class); final Task t4 = createStrictMock(Task.class); final Version fromVersion = Version.parseVersion("1.2.3"); ctx.setCurrentModule(mod); expect(d2.getTasks()).andReturn(Arrays.asList(t3, t4)); expect(d1.getTasks()).andReturn(Arrays.asList(t1, t2)); t1.execute(ctx); ctx.incExecutedTaskCount(); t2.execute(ctx); ctx.incExecutedTaskCount(); t3.execute(ctx); ctx.incExecutedTaskCount(); t4.execute(ctx); ctx.incExecutedTaskCount(); ctx.setCurrentModule(null); replay(ctx, d1, d2, t1, t2, t3, t4, moduleNode, versionProp, allModulesNode); final ModuleManager.ModuleAndDeltas moduleAndDeltas = new ModuleManager.ModuleAndDeltas(mod, fromVersion, Arrays.asList(d1, d2)); new ModuleManagerImpl(null, null, null, null, null).installOrUpdateModule(moduleAndDeltas, ctx); verify(ctx, d1, d2, t1, t2, t3, t4, moduleNode, versionProp, allModulesNode); } @Test public void testTaskExecutionExceptionInterruptsTasksAddsExplicitErrorMessage() throws TaskExecutionException { final ModuleDefinition mod = new ModuleDefinition("foo", Version.parseVersion("2.3.4"), null, null); final InstallContextImpl ctx = createStrictMock(InstallContextImpl.class); final Delta d1 = createStrictMock(Delta.class); final Task t1 = createStrictMock(Task.class); final Task t2 = createStrictMock(Task.class); ctx.setCurrentModule(mod); expect(d1.getTasks()).andReturn(Arrays.asList(t1, t2)); t1.execute(ctx); expectLastCall().andThrow(new TaskExecutionException("boo")); expect(t1.getName()).andReturn("task#1").anyTimes(); ctx.error(eq("Could not install or update foo module. Task 'task#1' failed. (TaskExecutionException: boo)"), isA(TaskExecutionException.class)); ctx.setCurrentModule(null); replay(ctx, d1, t1, t2); final ModuleManager.ModuleAndDeltas moduleAndDeltas = new ModuleManager.ModuleAndDeltas(mod, Version.parseVersion("1.2.3"), Arrays.asList(d1)); new ModuleManagerImpl().installOrUpdateModule(moduleAndDeltas, ctx); verify(ctx, d1, t1, t2); } @Test public void testFailedConditionsPreventsFurtherModulesToBeInstalledOrUpdated() throws TaskExecutionException, ModuleManagementException { final ModuleDefinitionReader modDefReader = createStrictMock(ModuleDefinitionReader.class); final InstallContextImpl ctx = createStrictMock(InstallContextImpl.class); final ModuleVersionHandler mvh1 = createStrictMock(ModuleVersionHandler.class); final ModuleVersionHandler mvh2 = createStrictMock(ModuleVersionHandler.class); final Delta d1 = createStrictMock(Delta.class); final Delta d2 = createStrictMock(Delta.class); final Condition c1 = createStrictMock(Condition.class); final Condition c2 = createStrictMock(Condition.class); final Condition c3 = createStrictMock(Condition.class); final Task t1 = createStrictMock(Task.class); final Task t2 = createStrictMock(Task.class); final ModuleDefinition mod1 = new ModuleDefinition("abc", Version.parseVersion("2.3.4"), null, null); final ModuleDefinition mod2 = new ModuleDefinition("xyz", Version.parseVersion("2.3.4"), null, null); final Map modMap = new HashMap(); modMap.put("abc", mod1); modMap.put("xyz", mod2); final Map<String, ModuleVersionHandler> moduleVersionHandlers = new HashMap<String, ModuleVersionHandler>(); moduleVersionHandlers.put("abc", mvh1); moduleVersionHandlers.put("xyz", mvh2); final Version v123 = Version.parseVersion("1.2.3"); // loading defs expect(modDefReader.readAll()).andReturn(modMap); // during checkForInstallOrUpdates() ctx.setCurrentModule(mod1); expect(mvh1.getCurrentlyInstalled(ctx)).andReturn(v123); expect(mvh1.getDeltas(ctx, v123)).andReturn(Collections.singletonList(d1)); expect(d1.getTasks()).andReturn(Collections.singletonList(t1)); ctx.setCurrentModule(mod2); expect(mvh2.getCurrentlyInstalled(ctx)).andReturn(v123); expect(mvh2.getDeltas(ctx, v123)).andReturn(Collections.singletonList(d2)); expect(d2.getTasks()).andReturn(Collections.singletonList(t2)); ctx.setCurrentModule(null); ctx.setTotalTaskCount(2); // during performInstallOrUpdate() expect(ctx.getStatus()).andReturn(null); ctx.setStatus(InstallStatus.inProgress); ctx.setCurrentModule(mod1); expect(d1.getConditions()).andReturn(Arrays.asList(c1, c2)); expect(c1.check(ctx)).andReturn(Boolean.FALSE); expect(c1.getDescription()).andReturn("Hi, please fix condition #1"); ctx.warn("Hi, please fix condition #1"); expect(c2.check(ctx)).andReturn(Boolean.TRUE); ctx.setCurrentModule(mod2); expect(d2.getConditions()).andReturn(Arrays.asList(c3)); expect(c3.check(ctx)).andReturn(Boolean.FALSE); expect(c3.getDescription()).andReturn("Hi, please fix condition #3 too"); ctx.warn("Hi, please fix condition #3 too"); ctx.setCurrentModule(null); ctx.setStatus(InstallStatus.stoppedConditionsNotMet); replay(modDefReader, ctx, mvh1, mvh2, d1, d2, c1, c2, c3, t1, t2); final ModuleManagerImpl moduleManager = new TestModuleManagerImpl(moduleVersionHandlers, ctx, modDefReader); moduleManager.loadDefinitions(); moduleManager.checkForInstallOrUpdates(); moduleManager.performInstallOrUpdate(); assertEquals("Conditions failed, so we still need to update/install", true, moduleManager.getStatus().needsUpdateOrInstall()); verify(modDefReader, ctx, mvh1, mvh2, d1, d2, c1, c2, c3, t1, t2); } @Test public void testPerformCantBeCalledTwiceByDifferentThreads() throws Exception { final ModuleDefinitionReader modDefReader = createStrictMock(ModuleDefinitionReader.class); final InstallContextImpl ctx = new InstallContextImpl(moduleRegistry); final ModuleVersionHandler mvh1 = createStrictMock(ModuleVersionHandler.class); final ModuleVersionHandler mvh2 = createStrictMock(ModuleVersionHandler.class); final Task t1 = new AbstractTask("sleep", "sleeeeep") { @Override public void execute(InstallContext installContext) throws TaskExecutionException { installContext.info("t1 executing"); try { Thread.sleep(500); } catch (InterruptedException e) { fail("can't test ... :("); } installContext.info("t1 executed"); } }; final Task t2 = createStrictMock(Task.class); final Delta d1 = DeltaBuilder.update(Version.parseVersion("1.0"), "", t1); final Delta d2 = DeltaBuilder.update(Version.parseVersion("2.0"), "", t2); final ModuleDefinition mod1 = new ModuleDefinition("abc", Version.parseVersion("2.3.4"), null, null); final ModuleDefinition mod2 = new ModuleDefinition("xyz", Version.parseVersion("2.3.4"), null, null); final Map modMap = new HashMap(); modMap.put("abc", mod1); modMap.put("xyz", mod2); final Map<String, ModuleVersionHandler> moduleVersionHandlers = new HashMap<String, ModuleVersionHandler>(); moduleVersionHandlers.put("abc", mvh1); moduleVersionHandlers.put("xyz", mvh2); final Version v123 = Version.parseVersion("1.2.3"); // loading defs expect(modDefReader.readAll()).andReturn(modMap); // during checkForInstallOrUpdates() expect(mvh1.getCurrentlyInstalled(ctx)).andReturn(v123); expect(mvh1.getDeltas(ctx, v123)).andReturn(Collections.singletonList(d1)); expect(mvh2.getCurrentlyInstalled(ctx)).andReturn(v123); expect(mvh2.getDeltas(ctx, v123)).andReturn(Collections.singletonList(d2)); // during performInstallOrUpdate() t2.execute(ctx); replay(modDefReader, mvh1, mvh2, t2); final ModuleManagerImpl moduleManager = new TestModuleManagerImpl(moduleVersionHandlers, ctx, modDefReader); moduleManager.loadDefinitions(); moduleManager.checkForInstallOrUpdates(); performInstallOrUpdateInThread(moduleManager, false); performInstallOrUpdateInThread(moduleManager, true); Thread.sleep(800); assertEquals(false, moduleManager.getStatus().needsUpdateOrInstall()); assertEquals(InstallStatus.installDone, ctx.getStatus()); assertEquals(1, ctx.getMessages().size()); final List msgs = ctx.getMessages().get(mod1.toString()); assertEquals(2, msgs.size()); assertEquals("t1 executing", ((InstallContext.Message) msgs.get(0)).getMessage()); assertEquals("t1 executed", ((InstallContext.Message) msgs.get(1)).getMessage()); verify(modDefReader, mvh1, mvh2, t2); } private void performInstallOrUpdateInThread(final ModuleManagerImpl moduleManager, final boolean shouldFail) { final Runnable runnable = new Runnable() { @Override public void run() { try { moduleManager.performInstallOrUpdate(); if (shouldFail) { fail("should have failed"); } } catch (IllegalStateException e) { if (shouldFail) { assertEquals("ModuleManager.performInstallOrUpdate() was already started !", e.getMessage()); } else { throw e; } } } }; new Thread(runnable).start(); } private static final class TestModuleManagerImpl extends ModuleManagerImpl { private final Map<String, ModuleVersionHandler> moduleVersionHandlers; protected TestModuleManagerImpl(Map<String, ModuleVersionHandler> moduleVersionHandlers, InstallContextImpl installContext, ModuleDefinitionReader moduleDefinitionReader) { super(installContext, moduleDefinitionReader); this.moduleVersionHandlers = moduleVersionHandlers; } @Override protected ModuleVersionHandler newVersionHandler(ModuleDefinition module) { return moduleVersionHandlers.get(module.getName()); } } }