content: /**
   * Copyright wro4j@2011
   */
  package ro.isdc.wro.model.group.processor;
  
  import static org.junit.Assert.assertEquals;
  import static org.mockito.Mockito.when;
  import static org.mockito.MockitoAnnotations.initMocks;
  import static ro.isdc.wro.util.WroTestUtils.compare;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.List;
  
  import javax.servlet.FilterConfig;
  import javax.servlet.ServletContext;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import org.apache.commons.io.IOUtils;
  import org.junit.After;
  import org.junit.AfterClass;
  import org.junit.Assert;
  import org.junit.Before;
  import org.junit.BeforeClass;
  import org.junit.Test;
  import org.mockito.Mock;
  import org.mockito.Mockito;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.WroRuntimeException;
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.config.jmx.WroConfiguration;
  import ro.isdc.wro.manager.factory.BaseWroManagerFactory;
  import ro.isdc.wro.manager.factory.WroManagerFactory;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.ResourceType;
  import ro.isdc.wro.model.resource.locator.UriLocator;
  import ro.isdc.wro.model.resource.locator.factory.SimpleUriLocatorFactory;
  import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
  import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.model.resource.processor.decorator.CopyrightKeeperProcessorDecorator;
  import ro.isdc.wro.model.resource.processor.factory.SimpleProcessorsFactory;
  import ro.isdc.wro.model.resource.processor.impl.js.JSMinProcessor;
  import ro.isdc.wro.util.StopWatch;
  import ro.isdc.wro.util.WroTestUtils;
  import ro.isdc.wro.util.WroUtil;
  
  
  /**
   * @author Alex Objelean
   */
  public class TestPreProcessorExecutor {
    private static final Logger LOG = LoggerFactory.getLogger(TestPreProcessorExecutor.class);
    @Mock
    private HttpServletRequest mockRequest;
    @Mock
    private HttpServletResponse mockResponse;
    @Mock
    private FilterConfig mockFilterConfig;
    @Mock
    private ServletContext mockServletContext;
    @Mock
    private UriLocatorFactory mockLocatorFactory;
    @Mock
    private UriLocator mockLocator;
    private PreProcessorExecutor victim;
  
    @BeforeClass
    public static void onBeforeClass() {
      assertEquals(0, Context.countActive());
    }
  
    @AfterClass
    public static void onAfterClass() {
      assertEquals(0, Context.countActive());
    }
  
    @Before
    public void setUp()
        throws Exception {
      initMocks(this);
  
      when(mockRequest.getRequestURL()).thenReturn(new StringBuffer(""));
      when(mockRequest.getServletPath()).thenReturn("");
      when(mockFilterConfig.getServletContext()).thenReturn(mockServletContext);
      when(mockLocatorFactory.locate(Mockito.anyString())).thenReturn(WroUtil.EMPTY_STREAM);
      when(mockLocator.locate(Mockito.anyString())).thenReturn(WroUtil.EMPTY_STREAM);
      when(mockLocatorFactory.getInstance(Mockito.anyString())).thenReturn(mockLocator);
  
      final Context context = Context.webContext(mockRequest, mockResponse, mockFilterConfig);
      Context.set(context);
      // force parallel execution
      Context.get().getConfig().setParallelPreprocessing(true);
      Context.get().getConfig().setIgnoreFailingProcessor(true);
      initExecutor();
    }
  
    private WroManagerFactory createWroManager(final ResourcePreProcessor... preProcessors) {
      final SimpleProcessorsFactory processorsFactory = new SimpleProcessorsFactory();
      for (final ResourcePreProcessor resourcePreProcessor : preProcessors) {
        processorsFactory.addPreProcessor(resourcePreProcessor);
      }
      final BaseWroManagerFactory wroManagerFactory = new BaseWroManagerFactory();
      wroManagerFactory.setProcessorsFactory(processorsFactory);
      wroManagerFactory.setUriLocatorFactory(mockLocatorFactory);
      return wroManagerFactory;
    }
  
    /**
     * @param wroManagerFactory
     */
    private void initExecutor(final ResourcePreProcessor... preProcessors) {
      final WroManagerFactory wroManagerFactory = createWroManager(preProcessors);
      final Injector injector = InjectorBuilder.create(wroManagerFactory).build();
      victim = new PreProcessorExecutor();
      injector.inject(victim);
    }
  
    @Test(expected = NullPointerException.class)
    public void cannotAcceptNullArguments()
        throws Exception {
      victim.processAndMerge(null, true);
    }
  
    /**
     * Creates a slow pre processor which sleeps for a given amount of milliseconds and doesn't change the processed
     * content.
     */
    private ResourcePreProcessor createSlowPreProcessor(final long time) {
      return new ResourcePreProcessor() {
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          try {
            IOUtils.copy(reader, writer);
            Thread.sleep(time);
          } catch (final InterruptedException e) {
          }
        }
      };
    }
  
    private ResourcePreProcessor createProcessorUsingMissingResource() {
      return new ResourcePreProcessor() {
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          LOG.debug("executing processor which will throw IOException");
          throw new IOException("Invalid resource found!");
        }
      };
    }
  
    private ResourcePreProcessor createProcessorWhichFails() {
      return new ResourcePreProcessor() {
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          LOG.debug("executing failing processor...");
          throw new WroRuntimeException("Boom!");
        }
      };
    }
  
    @Test
    public void processEmptyList()
        throws Exception {
      final List<Resource> resources = new ArrayList<Resource>();
      Assert.assertEquals("", victim.processAndMerge(resources, true));
      Assert.assertEquals("", victim.processAndMerge(resources, false));
    }
  
    @Test
    public void shouldNotFailWhenNoResourcesProcessed()
        throws Exception {
      initExecutor(createProcessorUsingMissingResource());
      victim.processAndMerge(createResources(), true);
    }
  
    private List<Resource> createResources(final Resource... resources) {
      final List<Resource> resourcesList = new ArrayList<Resource>();
      for (final Resource resource : resources) {
        resourcesList.add(resource);
      }
      return resourcesList;
    }
  
    @Test(expected = IOException.class)
    public void shouldFailWhenProcessingInvalidResource()
        throws Throwable {
      try {
        when(mockLocatorFactory.locate(Mockito.anyString())).thenThrow(IOException.class);
        Context.get().getConfig().setIgnoreFailingProcessor(false);
        shouldNotFailWhenProcessingInvalidResource();
      } catch (final WroRuntimeException e) {
        throw e.getCause();
      }
    }
  
    @Test
    public void shouldNotFailWhenProcessingInvalidResource()
        throws IOException {
      initExecutor(createProcessorUsingMissingResource());
      final List<Resource> resources = createResources(Resource.create("/uri", ResourceType.JS));
      final String result = victim.processAndMerge(resources, true);
      WroTestUtils.compare("", result);
    }
  
    @Test(expected = WroRuntimeException.class)
    public void shouldFailWhenUsingFailingPreProcessor()
        throws Exception {
      Context.get().getConfig().setIgnoreFailingProcessor(false);
      useFailingPreProcessor();
    }
  
    @Test
    public void shouldNotFailWhenUsingFailingPreProcessor()
        throws Exception {
      Context.get().getConfig().setIgnoreFailingProcessor(true);
      useFailingPreProcessor();
    }
  
    private void useFailingPreProcessor()
        throws Exception {
      initExecutor(createProcessorWhichFails());
      final List<Resource> resources = createResources(Resource.create("", ResourceType.JS));
      final String result = victim.processAndMerge(resources, true);
      WroTestUtils.compare("", result);
  
    }
  
    /**
     * This test should work when running at least on dual-core. It assumes that (P1(r1) + P2(r1) + P3(r1)) + (P1(r2) +
     * P2(r2) + P3(r2)) > Parallel(P1(r1) + P2(r1) + P3(r1) | P1(r2) + P2(r2) + P3(r2))
     */
    @Test
    public void preProcessingInParallelIsFaster()
        throws Exception {
      final int availableProcessors = Runtime.getRuntime().availableProcessors();
      LOG.info("availableProcessors: {}", availableProcessors);
      // test it only if number there are more than 1 CPU cores are available
      if (availableProcessors > 1) {
        final StopWatch watch = new StopWatch();
        final WroConfiguration config = Context.get().getConfig();
  
        initExecutor(createSlowPreProcessor(100), createSlowPreProcessor(100), createSlowPreProcessor(100));
        final List<Resource> resources = createResources(Resource.create("r1", ResourceType.JS),
            Resource.create("r2", ResourceType.JS));
  
        // warm up
        config.setParallelPreprocessing(true);
        victim.processAndMerge(resources, true);
  
        // parallel
        watch.start("parallel preProcessing");
        config.setParallelPreprocessing(true);
        victim.processAndMerge(resources, true);
        watch.stop();
        final long parallelExecution = watch.getLastTaskTimeMillis();
  
        // sequential
        config.setParallelPreprocessing(false);
        watch.start("sequential preProcessing");
        victim.processAndMerge(resources, true);
        watch.stop();
        final long sequentialExecution = watch.getLastTaskTimeMillis();
  
        final String message = "Processing details: \n" + watch.prettyPrint();
        LOG.debug(message);
  
        // prove that running in parallel is faster
        // delta indicates the improvement relative to parallel execution (we use 80% relative improvement, but it
        // normally
        // should be about 100%).
        final double delta = parallelExecution * 0.8;
        Assert.assertTrue(String.format("%s  > %s + %s", sequentialExecution, parallelExecution, delta),
            sequentialExecution > parallelExecution + delta);
      }
    }
  
    @Test
    public void shouldNotMinimizeDecoratedResourcesWithMinimizationDisabled()
        throws Exception {
      final List<Resource> resources = new ArrayList<Resource>();
      final Resource resource = Resource.create("classpath:1.js");
      resource.setMinimize(false);
      resources.add(resource);
      final ResourcePreProcessor preProcessor = CopyrightKeeperProcessorDecorator.decorate(new JSMinProcessor() {
        @Override
        public void process(final Resource resource, final Reader reader, final Writer writer)
            throws IOException {
          Assert.fail("Should not minimize");
        }
      });
      initExecutor(preProcessor);
      victim.processAndMerge(resources, true);
    }
  
    /**
     * When an empty resource is processed, the processing should not fail (warn only).
     */
    @Test
    public void shouldNotFailWhenEmptyResourceIsFound()
        throws Exception {
      final WroConfiguration config = Context.get().getConfig();
      config.setIgnoreMissingResources(false);
  
      final UriLocator emptyStreamLocator = new UriLocator() {
        public boolean accept(final String uri) {
          return true;
        }
  
        public InputStream locate(final String uri)
            throws IOException {
          return new ByteArrayInputStream("".getBytes());
        }
      };
      final UriLocatorFactory locatorFactory = new SimpleUriLocatorFactory().addLocator(emptyStreamLocator);
      // init executor
      final WroManagerFactory managerFactory = new BaseWroManagerFactory().setUriLocatorFactory(locatorFactory);
      InjectorBuilder.create(managerFactory).build().inject(victim);
  
      final List<Resource> resources = new ArrayList<Resource>();
      resources.add(Resource.create("/resource.js"));
      victim.processAndMerge(resources, true);
    }
  
    /**
     * @see https://code.google.com/p/wro4j/issues/detail?id=813
     */
    @Test
    public void shouldNotCommentMergedContentWhenLastLineContainsComment()
        throws Exception {
      final List<Resource> resources = new ArrayList<Resource>();
      resources.add(Resource.create("var a=1;//comment", ResourceType.JS));
      resources.add(Resource.create("a=2;", ResourceType.JS));
      final UriLocatorFactory locatorFactory = new SimpleUriLocatorFactory().addLocator(WroTestUtils.createResourceMockingLocator());
      final WroManagerFactory managerFactory = new BaseWroManagerFactory().setUriLocatorFactory(locatorFactory).setProcessorsFactory(
          new SimpleProcessorsFactory());
      InjectorBuilder.create(managerFactory).build().inject(victim);
  
      final String result = victim.processAndMerge(resources, false);
      compare("var a=1;//comment\na=2;\n", result);
    }
  
    @Test
    public void shouldNotAddRedundantNewLinesAfterMerge()
        throws Exception {
      final List<Resource> resources = new ArrayList<Resource>();
      resources.add(Resource.create("1\n\n", ResourceType.JS));
      resources.add(Resource.create("2", ResourceType.JS));
      final UriLocatorFactory locatorFactory = new SimpleUriLocatorFactory().addLocator(WroTestUtils.createResourceMockingLocator());
      final WroManagerFactory managerFactory = new BaseWroManagerFactory().setUriLocatorFactory(locatorFactory).setProcessorsFactory(
          new SimpleProcessorsFactory());
      InjectorBuilder.create(managerFactory).build().inject(victim);
  
      final String result = victim.processAndMerge(resources, false);
      compare("1\n2", result);
    }
  
    private static class AnyTypeProcessor
        implements ResourcePreProcessor, ResourcePostProcessor {
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
      }
  
      public void process(final Reader reader, final Writer writer)
          throws IOException {
      }
    }
  
    @Minimize
    private static class MinimizeAwareProcessor
        extends AnyTypeProcessor {
    }
  
    @After
    public void tearDown() {
      Context.unset();
    }
  }
  