Reconcile and CA for 'stack' property in manifest.yml

This commit is contained in:
Kris De Volder
2017-04-27 11:31:30 -07:00
parent d47fa48e43
commit f9722b85e5
17 changed files with 247 additions and 52 deletions

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client;
public interface CFStack extends CFEntity {
}

View File

@@ -18,5 +18,6 @@ public interface ClientRequests {
List<CFBuildpack> getBuildpacks() throws Exception;
List<CFServiceInstance> getServices() throws Exception;
List<CFDomain> getDomains() throws Exception;
List<CFStack> getStacks() throws Exception;
}

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import com.google.common.cache.CacheBuilder;
@@ -41,6 +42,7 @@ public class CFTarget {
private LoadingCache<String, List<CFBuildpack>> buildpacksCache;
private LoadingCache<String, List<CFServiceInstance>> servicesCache;
private LoadingCache<String, List<CFDomain>> domainCache;
private LoadingCache<String, List<CFStack>> stacksCache;
private CFCallableContext callableContext;
public CFTarget(String targetName, CFClientParams params, ClientRequests requests,
@@ -53,6 +55,20 @@ public class CFTarget {
}
private void initCache(ClientRequests requests) {
CacheLoader<String, List<CFStack>> stacksLoader = new CacheLoader<String, List<CFStack>>() {
@Override
public List<CFStack> load(String key) throws Exception {
/* Cache of services does not use keys, as the whole cache
* gets wiped clean on any new call to CF.
*/
return runAndCheckForFailure(() -> requests.getStacks());
}
};
this.stacksCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.SERVICES_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS).build(stacksLoader);
CacheLoader<String, List<CFServiceInstance>> servicesLoader = new CacheLoader<String, List<CFServiceInstance>>() {
@Override
@@ -64,7 +80,7 @@ public class CFTarget {
}
};
this.servicesCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.SERVICES_EXPIRATION, TimeUnit.SECONDS).build(servicesLoader);
.expireAfterAccess(CFTargetCache.SERVICES_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS).build(servicesLoader);
CacheLoader<String, List<CFBuildpack>> buildpacksLoader = new CacheLoader<String, List<CFBuildpack>>() {
@@ -77,7 +93,7 @@ public class CFTarget {
}
};
this.buildpacksCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION, TimeUnit.HOURS).build(buildpacksLoader);
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS).build(buildpacksLoader);
CacheLoader<String, List<CFDomain>> domainLoader = new CacheLoader<String, List<CFDomain>>() {
@@ -88,7 +104,7 @@ public class CFTarget {
};
this.domainCache = CacheBuilder.newBuilder()
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION, TimeUnit.HOURS).build(domainLoader);
.expireAfterAccess(CFTargetCache.TARGET_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS).build(domainLoader);
}
protected <T> T runAndCheckForFailure(Callable<T> callable) throws Exception {
@@ -103,6 +119,15 @@ public class CFTarget {
return params;
}
public List<CFStack> getStacks() throws Exception {
// Use the target name as the "key" , since Guava cache doesn't allow null keys
// However, the key is not really used when fetching buildpacks, as we are not caching
// buildpacks per target here. This class only represents ONE target, so it will only
// ever have one key
String key = getName();
return this.stacksCache.get(key);
}
public List<CFBuildpack> getBuildpacks() throws Exception {
// Use the target name as the "key" , since Guava cache doesn't allow null keys
// However, the key is not really used when fetching buildpacks, as we are not caching

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -30,8 +31,8 @@ public class CFTargetCache {
private final LoadingCache<ClientParamsCacheKey, CFTarget> cache;
private final CFCallableContext cacheCallableContext;
public static final long SERVICES_EXPIRATION = 10;
public static final long TARGET_EXPIRATION = 1;
public static final Duration SERVICES_EXPIRATION = Duration.ofSeconds(10);
public static final Duration TARGET_EXPIRATION = Duration.ofHours(1);
public CFTargetCache(ClientParamsProvider paramsProvider, CloudFoundryClientFactory clientFactory,
ClientTimeouts timeouts) {
@@ -49,7 +50,7 @@ public class CFTargetCache {
}
};
cache = CacheBuilder.newBuilder().maximumSize(1).expireAfterAccess(TARGET_EXPIRATION, TimeUnit.HOURS)
cache = CacheBuilder.newBuilder().maximumSize(1).expireAfterAccess(TARGET_EXPIRATION.toMillis(), TimeUnit.MILLISECONDS)
.build(loader);
}

View File

@@ -14,10 +14,12 @@ import org.cloudfoundry.operations.buildpacks.Buildpack;
import org.cloudfoundry.operations.domains.Domain;
import org.cloudfoundry.operations.services.ServiceInstanceSummary;
import org.cloudfoundry.operations.services.ServiceInstanceType;
import org.cloudfoundry.operations.stacks.Stack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFEntities;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
/**
* Various helper methods to 'wrap' objects returned by CF client into our own
@@ -27,17 +29,16 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInsta
*/
public class CFWrappingV2 {
public static CFBuildpack wrap(Buildpack buildpack) {
String name = buildpack.getName();
return CFEntities.createBuildpack(name);
}
public static CFDomain wrap(Domain domain) {
String name = domain.getName();
return CFEntities.createDomain(name);
}
public static CFServiceInstance wrap(ServiceInstanceSummary serviceInstance) {
String name = serviceInstance.getName();
String plan = serviceInstance.getPlan();
@@ -47,4 +48,22 @@ public class CFWrappingV2 {
return CFEntities.createServiceInstance(name, service, plan);
}
public static CFStack wrap(Stack stack) {
if (stack!=null) {
String name = stack.getName();
return new CFStack() {
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return "CFStack("+name+")";
}
};
}
return null;
}
}

View File

@@ -20,6 +20,7 @@ import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
@@ -83,7 +84,7 @@ public class DefaultClientRequestsV2 implements ClientRequests {
)
);
}
@Override
public List<CFDomain> getDomains() throws Exception {
@@ -98,7 +99,7 @@ public class DefaultClientRequestsV2 implements ClientRequests {
)
);
}
@Override
public List<CFBuildpack> getBuildpacks() throws Exception {
return ReactorUtils.get(timeouts.getBuildpacksTimeout(), CancelationTokens.NULL,
@@ -112,6 +113,18 @@ public class DefaultClientRequestsV2 implements ClientRequests {
)
);
}
@Override
public List<CFStack> getStacks() throws Exception {
return ReactorUtils.get(
log("operations.stacks().list()",
_operations.stacks()
.list()
.map(CFWrappingV2::wrap)
.collectList()
)
);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//// calls to client and operations with 'logging'.

View File

@@ -10,13 +10,14 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.completion;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.DEEMP_EXISTS;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +30,7 @@ import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.ValueParseException;
import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal;
import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
@@ -42,16 +44,11 @@ import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SChildBearingNode;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
import org.springframework.ide.vscode.commons.yaml.util.Streams;
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import static org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal.*;
public class YTypeAssistContext extends AbstractYamlAssistContext {
final static Logger logger = LoggerFactory.getLogger(YTypeAssistContext.class);

View File

@@ -16,6 +16,7 @@ import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ConnectionException;
@@ -78,7 +79,7 @@ public abstract class AbstractCFHintsProvider implements Callable<Collection<YVa
}
/**
*
*
* @param e
* @return an error that requires no additional information when showing its
* message, or null if no such error is found
@@ -87,7 +88,7 @@ public abstract class AbstractCFHintsProvider implements Callable<Collection<YVa
return ExceptionUtil.findThrowable(e,
ImmutableList.of(NoTargetsException.class, ConnectionException.class));
}
/**
*
* @return non-null list of hints. Return empty if no hints available

View File

@@ -29,16 +29,13 @@ public class CFServicesValueParser extends EnumValueParser {
}
@Override
protected String createBlankTextErrorMessage() {
return "At least one service instance name must be specified";
}
protected Exception errorOnParse(String message) {
// Parse errors should be indicated differently than regular schema
// problems (e.g. unknown service may be a warning)
return new ReconcileException(message, ManifestYamlSchemaProblemsTypes.UNKNOWN_SERVICES_PROBLEM);
}
@Override
protected Exception errorOnBlank(String message) {
// Blank errors should be regular schema problems
return new ReconcileException(message, YamlSchemaProblems.SCHEMA_PROBLEM);

View File

@@ -59,12 +59,12 @@ public class ManifestYamlCFServicesProvider extends AbstractCFHintsProvider {
return hints;
}
private String getServiceLabel(CFTarget cfClientTarget, CFServiceInstance service) {
return service.getName() + " - " + service.getPlan();
}
@Override
protected String getTypeName() {
return "Service";
}
protected final String getServiceLabel(CFTarget cfClientTarget, CFServiceInstance service) {
return service.getName() + " - " + service.getPlan();
}
}

View File

@@ -95,9 +95,10 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
}
protected ManifestYmlHintProviders getHintProviders() {
Callable<Collection<YValueHint>> buildPacksProvider = getBuildpacksProvider();
Callable<Collection<YValueHint>> servicesProvider = getServicesProvider();
Callable<Collection<YValueHint>> domainsProvider = getDomainsProvider();
Callable<Collection<YValueHint>> buildPacksProvider = new ManifestYamlCFBuildpacksProvider(getCfTargetCache());
Callable<Collection<YValueHint>> servicesProvider = new ManifestYamlCFServicesProvider(getCfTargetCache());
Callable<Collection<YValueHint>> domainsProvider = new ManifestYamlCFDomainsProvider(getCfTargetCache());
Callable<Collection<YValueHint>> stacksProvider = new ManifestYamlStacksProvider(getCfTargetCache());
return new ManifestYmlHintProviders() {
@@ -115,6 +116,11 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
public Callable<Collection<YValueHint>> getBuildpackProviders() {
return buildPacksProvider;
}
@Override
public Callable<Collection<YValueHint>> getStacksProvider() {
return stacksProvider;
}
};
}
@@ -127,16 +133,4 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
return cfTargetCache;
}
private Callable<Collection<YValueHint>> getBuildpacksProvider() {
return new ManifestYamlCFBuildpacksProvider(getCfTargetCache());
}
private Callable<Collection<YValueHint>> getServicesProvider() {
return new ManifestYamlCFServicesProvider(getCfTargetCache());
}
private Callable<Collection<YValueHint>> getDomainsProvider() {
return new ManifestYamlCFDomainsProvider(getCfTargetCache());
}
}

View File

@@ -15,9 +15,6 @@ import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaPr
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
/**
*
*/
public class ManifestYamlSchemaProblemsTypes {
public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem",
@@ -25,5 +22,8 @@ public class ManifestYamlSchemaProblemsTypes {
public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem",
ProblemSeverity.WARNING);
public static final ProblemType UNKNOWN_STACK_PROBLEM = problemType("UnknownStackProblem",
ProblemSeverity.WARNING);;
}

View File

@@ -0,0 +1,66 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.manifest.yaml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
public class ManifestYamlStacksProvider extends AbstractCFHintsProvider {
public ManifestYamlStacksProvider(CFTargetCache targetCache) {
super(targetCache);
}
@Override
protected String getTypeName() {
return "Stack";
}
@Override
protected Collection<YValueHint> getHints(List<CFTarget> targets) throws Exception {
// NOTE: empty list of services is a VALID result. A CF target may have
// no service instances
// created, so if empty list is returned from the client, then RETURN empty list. don't
// return null
// for empty services cases
List<YValueHint> hints = new ArrayList<>();
for (CFTarget cfTarget : targets) {
List<CFStack> stacks = cfTarget.getStacks();
Renderable targetLabel = Renderables.text(cfTarget.getLabel());
if (stacks != null && !stacks.isEmpty()) {
for (CFStack s : stacks) {
String name = s.getName();
String label = name;
YValueHint hint = new BasicYValueHint(name, label)
.setDocumentation(targetLabel);
if (!hints.contains(hint)) {
hints.add(hint);
}
}
return hints;
}
}
return hints;
}
}

View File

@@ -23,4 +23,6 @@ public interface ManifestYmlHintProviders {
Callable<Collection<YValueHint>> getDomainsProvider();
Callable<Collection<YValueHint>> getStacksProvider();
}

View File

@@ -14,6 +14,8 @@ import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Callable;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.IntegerRange;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
@@ -37,7 +39,6 @@ public class ManifestYmlSchema implements YamlSchema {
private final AbstractType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
private final Callable<Collection<YValueHint>> buildpackProvider;
private static final Set<String> TOPLEVEL_EXCLUDED = ImmutableSet.of(
"name", "host", "hosts"
@@ -50,9 +51,10 @@ public class ManifestYmlSchema implements YamlSchema {
public ManifestYmlSchema(ManifestYmlHintProviders providers) {
this.buildpackProvider = providers.getBuildpackProviders();
Callable<Collection<YValueHint>> buildpackProvider = providers.getBuildpackProviders();
Callable<Collection<YValueHint>> servicesProvider = providers.getServicesProvider();
Callable<Collection<YValueHint>> domainsProvider = providers.getDomainsProvider();
Callable<Collection<YValueHint>> stacksProvider = providers.getStacksProvider();
YTypeFactory f = new YTypeFactory();
@@ -65,11 +67,20 @@ public class ManifestYmlSchema implements YamlSchema {
YAtomicType t_path = f.yatomic("Path");
YAtomicType t_buildpack = f.yatomic("Buildpack");
if (this.buildpackProvider != null) {
t_buildpack.addHintProvider(this.buildpackProvider);
if (buildpackProvider != null) {
t_buildpack.addHintProvider(buildpackProvider);
// t_buildpack.parseWith(ManifestYmlValueParsers.fromHints(t_buildpack.toString(), buildpackProvider));
}
YAtomicType t_stack = f.yatomic("Stack");
t_stack.addHintProvider(stacksProvider);
t_stack.parseWith(new EnumValueParser(t_stack.toString(), YTypeFactory.valuesFromHintProvider(stacksProvider)) {
@Override
protected Exception errorOnParse(String message) {
return new ReconcileException(message, ManifestYamlSchemaProblemsTypes.UNKNOWN_STACK_PROBLEM);
}
});
YAtomicType t_domain = f.yatomic("Domain");
if (domainsProvider != null) {
@@ -136,7 +147,7 @@ public class ManifestYmlSchema implements YamlSchema {
f.yprop("random-route", t_boolean),
f.yprop("routes", f.yseq(route)),
f.yprop("services", f.yseq(t_service)),
f.yprop("stack", t_string),
f.yprop("stack", t_stack),
f.yprop("timeout", t_pos_integer),
f.yprop("health-check-type", t_health_check_type),
f.yprop("health-check-http-endpoint", t_ne_string)

View File

@@ -29,6 +29,7 @@ import org.mockito.Mockito;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFDomain;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
@@ -589,6 +590,51 @@ public class ManifestYamlEditorTest {
editor.assertNoHover("otherdomain.org");
}
@Test public void stacksCompletion() throws Exception {
List<CFStack> stacks = ImmutableList.of(
mockStack("linux"), mockStack("windows")
);
when(cloudfoundry.client.getStacks()).thenReturn(stacks);
Editor editor = harness.newEditor(
"stack: <*>"
);
CompletionItem c = editor.assertCompletions(
"stack: linux<*>",
"stack: windows<*>"
).get(0);
assertEquals("an-org : a-space [test.io]", c.getDocumentation());
}
@Test public void stacksReconcile() throws Exception {
List<CFStack> stacks = ImmutableList.of(
mockStack("linux"), mockStack("windows")
);
when(cloudfoundry.client.getStacks()).thenReturn(stacks);
{
Editor editor = harness.newEditor(
"stack: android<*>"
);
Diagnostic p = editor.assertProblems("android|'android' is an unknown 'Stack'. Valid values are: [linux, windows]").get(0);
assertEquals(DiagnosticSeverity.Warning, p.getSeverity());
}
{
Editor editor = harness.newEditor(
"stack: <*>"
);
Diagnostic p = editor.assertProblems("|'Stack' cannot be blank").get(0);
assertEquals(DiagnosticSeverity.Error, p.getSeverity());
}
}
private CFStack mockStack(String name) {
CFStack stack = Mockito.mock(CFStack.class);
when(stack.getName()).thenReturn(name);
return stack;
}
@Test
public void reconcileDuplicateKeys() throws Exception {
Editor editor = harness.newEditor(
@@ -882,10 +928,12 @@ public class ManifestYamlEditorTest {
ClientRequests cfClient = cloudfoundry.client;
when(cfClient.getBuildpacks()).thenThrow(new IOException("Can't get buildpacks"));
when(cfClient.getServices()).thenThrow(new IOException("Can't get services"));
when(cfClient.getStacks()).thenThrow(new IOException("Can't get stacks"));
Editor editor = harness.newEditor(
"applications:\n" +
"- name: foo\n" +
" buildpack: bad-buildpack\n" +
" stack: bad-stack\n" +
" services:\n" +
" - bad-service\n" +
" bogus: bad" //a token error to make sure reconciler is actually running!

View File

@@ -174,5 +174,10 @@ public class ManifestYmlSchemaTest {
public Callable<Collection<YValueHint>> getBuildpackProviders() {
return null;
}
@Override
public Callable<Collection<YValueHint>> getStacksProvider() {
return null;
}
};
}