/* * Copyright 2012-2017 the original author or authors. * * 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 org.springframework.boot.devtools.remote.client; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.net.ConnectException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.springframework.boot.devtools.classpath.ClassPathChangedEvent; import org.springframework.boot.devtools.filewatch.ChangedFile; import org.springframework.boot.devtools.filewatch.ChangedFile.Type; import org.springframework.boot.devtools.filewatch.ChangedFiles; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceFolder; import org.springframework.boot.devtools.test.MockClientHttpRequestFactory; import org.springframework.http.HttpStatus; import org.springframework.mock.http.client.MockClientHttpRequest; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ClassPathChangeUploader}. * * @author Phillip Webb * @author Andy Wilkinson */ public class ClassPathChangeUploaderTests { @Rule public ExpectedException thrown = ExpectedException.none(); @Rule public TemporaryFolder temp = new TemporaryFolder(); private MockClientHttpRequestFactory requestFactory; private ClassPathChangeUploader uploader; @Before public void setup() { this.requestFactory = new MockClientHttpRequestFactory(); this.uploader = new ClassPathChangeUploader("http://localhost/upload", this.requestFactory); } @Test public void urlMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("URL must not be empty"); new ClassPathChangeUploader(null, this.requestFactory); } @Test public void urlMustNotBeEmpty() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("URL must not be empty"); new ClassPathChangeUploader("", this.requestFactory); } @Test public void requestFactoryMustNotBeNull() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("RequestFactory must not be null"); new ClassPathChangeUploader("http://localhost:8080", null); } @Test public void urlMustNotBeMalformed() throws Exception { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Malformed URL 'htttttp:///ttest'"); new ClassPathChangeUploader("htttttp:///ttest", this.requestFactory); } @Test public void sendsClassLoaderFiles() throws Exception { File sourceFolder = this.temp.newFolder(); ClassPathChangedEvent event = createClassPathChangedEvent(sourceFolder); this.requestFactory.willRespond(HttpStatus.OK); this.uploader.onApplicationEvent(event); assertThat(this.requestFactory.getExecutedRequests()).hasSize(1); MockClientHttpRequest request = this.requestFactory.getExecutedRequests().get(0); verifyUploadRequest(sourceFolder, request); } @Test public void retriesOnConnectException() throws Exception { File sourceFolder = this.temp.newFolder(); ClassPathChangedEvent event = createClassPathChangedEvent(sourceFolder); this.requestFactory.willRespond(new ConnectException()); this.requestFactory.willRespond(HttpStatus.OK); this.uploader.onApplicationEvent(event); assertThat(this.requestFactory.getExecutedRequests()).hasSize(2); verifyUploadRequest(sourceFolder, this.requestFactory.getExecutedRequests().get(1)); } private void verifyUploadRequest(File sourceFolder, MockClientHttpRequest request) throws IOException, ClassNotFoundException { ClassLoaderFiles classLoaderFiles = deserialize(request.getBodyAsBytes()); Collection<SourceFolder> sourceFolders = classLoaderFiles.getSourceFolders(); assertThat(sourceFolders.size()).isEqualTo(1); SourceFolder classSourceFolder = sourceFolders.iterator().next(); assertThat(classSourceFolder.getName()).isEqualTo(sourceFolder.getAbsolutePath()); Iterator<ClassLoaderFile> classFiles = classSourceFolder.getFiles().iterator(); assertClassFile(classFiles.next(), "File1", ClassLoaderFile.Kind.ADDED); assertClassFile(classFiles.next(), "File2", ClassLoaderFile.Kind.MODIFIED); assertClassFile(classFiles.next(), null, ClassLoaderFile.Kind.DELETED); assertThat(classFiles.hasNext()).isFalse(); } private void assertClassFile(ClassLoaderFile file, String content, Kind kind) { assertThat(file.getContents()) .isEqualTo(content == null ? null : content.getBytes()); assertThat(file.getKind()).isEqualTo(kind); } private ClassPathChangedEvent createClassPathChangedEvent(File sourceFolder) throws IOException { Set<ChangedFile> files = new LinkedHashSet<>(); File file1 = createFile(sourceFolder, "File1"); File file2 = createFile(sourceFolder, "File2"); File file3 = createFile(sourceFolder, "File3"); files.add(new ChangedFile(sourceFolder, file1, Type.ADD)); files.add(new ChangedFile(sourceFolder, file2, Type.MODIFY)); files.add(new ChangedFile(sourceFolder, file3, Type.DELETE)); Set<ChangedFiles> changeSet = new LinkedHashSet<>(); changeSet.add(new ChangedFiles(sourceFolder, files)); ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false); return event; } private File createFile(File sourceFolder, String name) throws IOException { File file = new File(sourceFolder, name); FileCopyUtils.copy(name.getBytes(), file); return file; } private ClassLoaderFiles deserialize(byte[] bytes) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream( new ByteArrayInputStream(bytes)); return (ClassLoaderFiles) objectInputStream.readObject(); } }