code: /*
   * Copyright © 2014-2017 EntIT Software LLC, a Micro Focus company (L.P.)
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package io.cloudslang.runtime.impl.python;
  
  import io.cloudslang.dependency.api.services.DependencyService;
  import io.cloudslang.dependency.api.services.MavenConfig;
  import io.cloudslang.dependency.impl.services.MavenConfigImpl;
  import io.cloudslang.runtime.impl.AbsExecutionCachedEngineTest;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.test.context.ContextConfiguration;
  import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  
  import java.io.Serializable;
  import java.util.*;
  
  import static org.junit.Assert.*;
  import static org.mockito.Mockito.*;
  
  /**
   * Created by Genadi Rabinovich, genadi@hpe.com on 05/05/2016.
   */
  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes = PythonExecutionCachedEngineTest.TestConfig.class)
  public class PythonExecutionCachedEngineTest extends AbsExecutionCachedEngineTest {
      static {
          System.setProperty("python.executor.provider", PythonExecutionCachedEngine.class.getSimpleName());
          System.setProperty("python.executor.cache.size", "3");
      }
  
      @Autowired
      private PythonExecutionEngine pythonExecutionEngine;
  
      @Test
      public void testJavaCachedExecutorProviderMultiThreadedTest() throws InterruptedException {
          testCachedExecutorEngineMultiThreaded((PythonExecutionCachedEngineAllocator) pythonExecutionEngine);
      }
  
      @Test
      public void testJavaCachedExecutorProviderTest() {
          testLeastRecentlyUse((PythonExecutionCachedEngineAllocator) pythonExecutionEngine);
      }
  
      @Test
      public void testPythonExecutorRelease() {
          PythonExecutionCachedEngineAllocator executionEngineAllocator = (PythonExecutionCachedEngineAllocator) pythonExecutionEngine;
          final Set<String> dependencies1 = new HashSet<>(Arrays.asList("g1:a2:v3", "g2:a3:v4"));
          PythonExecutor executor1 = executionEngineAllocator.allocateExecutor(dependencies1);
          assertFalse(executor1.isClosed());
  
          // L-> 1
          final Set<String> dependencies2 = new HashSet<>(Arrays.asList("g2:a3:v4", "g3:a4:v5"));
          PythonExecutor executor2 = executionEngineAllocator.allocateExecutor(dependencies2);
          assertFalse(executor2.isClosed());
          // L-> 1 -> 2
          assertNotEquals(executor1, executor2);
  
          final Set<String> dependencies3 = new HashSet<>(Arrays.asList("g3:a4:v5", "g4:a5:v6"));
          PythonExecutor executor3 = executionEngineAllocator.allocateExecutor(dependencies3);
          assertFalse(executor3.isClosed());
          // L-> 1 -> 2 -> 3
          assertNotEquals(executor1, executor3);
          assertNotEquals(executor2, executor3);
  
          // already cached
          executionEngineAllocator.allocateExecutor(dependencies1);
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
  
          executionEngineAllocator.allocateExecutor(new HashSet<>(Arrays.asList("g2:a3:v4", "g1:a2:v3")));
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          // L-> 2 -> 3 -> 1
  
          // already cached
          executionEngineAllocator.allocateExecutor(dependencies2);
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
  
          executionEngineAllocator.allocateExecutor(new HashSet<>(Arrays.asList("g3:a4:v5", "g2:a3:v4")));
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          // L-> 3 -> 1 -> 2
  
          // already cached
          executionEngineAllocator.allocateExecutor(dependencies3);
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
  
          executionEngineAllocator.allocateExecutor(new HashSet<>(Arrays.asList("g4:a5:v6", "g3:a4:v5")));
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          // L-> 1 -> 2 -> 3
  
  
          // new one, should remove javaExecutor1
          final Set<String> dependencies4 = new HashSet<>(Arrays.asList("g4:a5:v6", "g5:a6:v7"));
          PythonExecutor executor4 = executionEngineAllocator.allocateExecutor(dependencies4);
          // L-> 2 -> 3 -> 4
          assertNotEquals(executor1, executor4);
          assertNotEquals(executor2, executor4);
          assertNotEquals(executor3, executor4);
  
          //executor1 was removed from cache and marked for close but not closed yet since it is in use
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          assertFalse(executor4.isClosed());
  
          executionEngineAllocator.releaseExecutor(executor1);
          //executor1 is released only once but was allocated 3 times
          assertFalse(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          assertFalse(executor4.isClosed());
  
          executionEngineAllocator.releaseExecutor(executor1);
          executionEngineAllocator.releaseExecutor(executor1);
          //executor1 is released all 3 time and should be closed
          assertTrue(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          assertFalse(executor4.isClosed());
  
          executionEngineAllocator.releaseExecutor(executor2);
          executionEngineAllocator.releaseExecutor(executor2);
          executionEngineAllocator.releaseExecutor(executor2);
  
          executionEngineAllocator.releaseExecutor(executor3);
          executionEngineAllocator.releaseExecutor(executor3);
          executionEngineAllocator.releaseExecutor(executor3);
  
          executionEngineAllocator.releaseExecutor(executor4);
  
          //executor2, executor3, executor4 are released but not closed yet
          assertTrue(executor1.isClosed());
          assertFalse(executor2.isClosed());
          assertFalse(executor3.isClosed());
          assertFalse(executor4.isClosed());
  
          executor2.close();
          executor3.close();
          executor4.close();
  
          //executor2, executor3, executor4 are closed now as well
          assertTrue(executor1.isClosed());
          assertTrue(executor2.isClosed());
          assertTrue(executor3.isClosed());
          assertTrue(executor4.isClosed());
      }
  
      @Test
      public void testPythonExecutorReleasedAfterSuccessExecution() {
          final PythonExecutor pythonExecutor = mock(PythonExecutor.class);
          PythonExecutionCachedEngine engine = new PythonExecutionCachedEngine() {
              public PythonExecutor allocateExecutor(Set<String> dependencies) {
                  return pythonExecutor;
              }
          };
          engine.exec(Collections.<String>emptySet(),"", new HashMap<String, Serializable>());
          verify(pythonExecutor).release();
      }
  
      @Test
      public void testPythonExecutorReleasedAfterException() {
          final String script = "";
          final Map<String, Serializable> args = new HashMap<>();
          final PythonExecutor pythonExecutor = mock(PythonExecutor.class);
          when(pythonExecutor.exec(script, args)).thenThrow(new IllegalArgumentException(""));
          PythonExecutionCachedEngine engine = new PythonExecutionCachedEngine() {
              public PythonExecutor allocateExecutor(Set<String> dependencies) {
                  return pythonExecutor;
              }
          };
          try {
              engine.exec(Collections.<String>emptySet(),script, args);
          } catch (Throwable t) {
              assertTrue(t instanceof IllegalArgumentException);
          }
          verify(pythonExecutor).release();
      }
  
      @Configuration
      static class TestConfig {
          @Bean
          public PythonExecutionEngine javaExecutorProvider() {return new PythonExecutionCachedEngineAllocator();}
  
          @Bean
          public DependencyService dependencyService() {return new DependencyService() {
              @Override
              public Set<String> getDependencies(Set<String> resources) {
                  return new HashSet<>(Arrays.asList("c:\\a.jar", "c:\\b.jar"));
              }
          };}
  
          @Bean
          public MavenConfig mavenConfig() {return new MavenConfigImpl();}
      }
  
      private static class PythonExecutionCachedEngineAllocator extends PythonExecutionCachedEngine {
          public PythonExecutor allocateExecutor(Set<String> dependencies) {
              return super.allocateExecutor(dependencies);
          }
  
          public void releaseExecutor(PythonExecutor executor) {
              super.releaseExecutor(executor);
          }
      }
  }
  