8059349: Public API scanning should be implemented in the form of a TaskListener
authoralundblad
Tue, 07 Oct 2014 21:21:42 +0200
changeset 26992 92e69fa21956
parent 26991 88d998b3bb4b
child 26993 513b2cae81c3
8059349: Public API scanning should be implemented in the form of a TaskListener Summary: Replaces JavaCompilerWithDeps with a TaskListener. Reviewed-by: jfranck
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/JavaCompilerWithDeps.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacErrorHandler.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java
langtools/test/tools/sjavac/PackagePathMismatch.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Tue Oct 07 21:15:10 2014 +0200
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Tue Oct 07 21:21:42 2014 +0200
@@ -1228,12 +1228,6 @@
                 attr.postAttr(env.tree);
             }
             compileStates.put(env, CompileState.ATTR);
-            if (rootClasses != null && rootClasses.contains(env.enclClass)) {
-                // This was a class that was explicitly supplied for compilation.
-                // If we want to capture the public api of this class,
-                // then now is a good time to do it.
-                reportPublicApi(env.enclClass.sym);
-            }
         }
         finally {
             log.useSource(prev);
@@ -1242,14 +1236,6 @@
         return env;
     }
 
-    /** Report the public api of a class that was supplied explicitly for compilation,
-     *  for example on the command line to javac.
-     * @param sym The symbol of the class.
-     */
-    public void reportPublicApi(ClassSymbol sym) {
-       // Override to collect the reported public api.
-    }
-
     /**
      * Perform dataflow checks on attributed parse trees.
      * These include checks for definite assignment and unreachable statements.
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/JavaCompilerWithDeps.java	Tue Oct 07 21:15:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.sun.tools.sjavac.comp;
-
-import java.util.StringTokenizer;
-
-import com.sun.tools.javac.main.JavaCompiler;
-import com.sun.tools.javac.util.Context;
-import com.sun.tools.javac.code.Symbol.ClassSymbol;
-
-/** Subclass to Resolve that overrides collect.
- *
- *  <p><b>This is NOT part of any supported API.
- *  If you write code that depends on this, you do so at your own risk.
- *  This code and its internal interfaces are subject to change or
- *  deletion without notice.</b>
- */
-public class JavaCompilerWithDeps extends JavaCompiler {
-
-    /** The dependency database
-     */
-    protected Dependencies deps;
-    protected SjavacErrorHandler errorHandler;
-
-    public JavaCompilerWithDeps(Context context, SjavacErrorHandler eh) {
-        super(context);
-        deps = Dependencies.instance(context);
-        errorHandler = eh;
-        needRootClasses = true;
-    }
-
-    public static void preRegister(Context context, final SjavacErrorHandler eh) {
-        context.put(compilerKey, new Context.Factory<JavaCompiler>() {
-            public JavaCompiler make(Context c) {
-                JavaCompiler instance = new JavaCompilerWithDeps(c, eh);
-                c.put(JavaCompiler.class, instance);
-                return instance;
-            }
-        });
-    }
-
-    /** Collect the public apis of classes supplied explicitly for compilation.
-     * @param sym The class to visit.
-     */
-    @Override
-    public void reportPublicApi(ClassSymbol sym) {
-        // The next test will catch when source files are located in the wrong directory!
-        // This ought to be moved into javac as a new warning, or perhaps as part
-        // of the auxiliary class warning.
-
-        // For example if sun.swing.BeanInfoUtils
-        // is in fact stored in: /mybuild/jdk/gensrc/javax/swing/beaninfo/BeanInfoUtils.java
-
-        // We do not need to test that BeanInfoUtils is stored in a file named BeanInfoUtils
-        // since this is checked earlier.
-        if (sym.sourcefile != null) {
-            // Rewrite sun.swing.BeanInfoUtils into /sun/swing/
-            StringBuilder pathb = new StringBuilder();
-            StringTokenizer qn = new StringTokenizer(sym.packge().toString(), ".");
-            boolean first = true;
-            while (qn.hasMoreTokens()) {
-                String o = qn.nextToken();
-                pathb.append("/");
-                pathb.append(o);
-                first = false;
-            }
-            pathb.append("/");
-            String path = pathb.toString();
-
-            // Now cut the uri to be: file:///mybuild/jdk/gensrc/javax/swing/beaninfo/
-            String p = sym.sourcefile.toUri().getPath();
-            // Do not use File.separatorChar here, a URI always uses slashes /.
-            int i = p.lastIndexOf("/");
-            String pp = p.substring(0,i+1);
-
-            // Now check if the truncated uri ends with the path. (It does not == failure!)
-            if (path.length() > 0 && !path.equals("/unnamed package/") && !pp.endsWith(path)) {
-                errorHandler.logError("Error: The source file "+sym.sourcefile.getName()+
-                                        " is located in the wrong package directory, because it contains the class "+
-                                        sym.getQualifiedName());
-            }
-        }
-        deps.visitPubapi(sym);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PathAndPackageVerifier.java	Tue Oct 07 21:21:42 2014 +0200
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.tools.sjavac.comp;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.tools.JavaFileObject;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+import com.sun.tools.javac.util.Name;
+
+public class PathAndPackageVerifier implements TaskListener {
+
+    // Stores the set of compilation units whose source file path does not
+    // match the package declaration.
+    Set<CompilationUnitTree> misplacedCompilationUnits = new HashSet<>();
+
+    @Override
+    @DefinedBy(Api.COMPILER_TREE)
+    public void started(TaskEvent e) {
+    }
+
+    @Override
+    @DefinedBy(Api.COMPILER_TREE)
+    public void finished(TaskEvent e) {
+        if (e.getKind() != TaskEvent.Kind.ANALYZE)
+            return;
+
+        CompilationUnitTree cu = e.getCompilationUnit();
+        if (cu == null)
+            return;
+
+        JavaFileObject jfo = cu.getSourceFile();
+        if (jfo == null)
+            return; // No source file -> package doesn't matter
+
+        JCTree pkg = (JCTree) cu.getPackageName();
+        if (pkg == null)
+            return; // Default package. See JDK-8048144.
+
+        Path dir = Paths.get(jfo.toUri()).normalize().getParent();
+        if (!checkPathAndPackage(dir, pkg))
+            misplacedCompilationUnits.add(cu);
+    }
+
+    /* Returns true if dir matches pkgName.
+     *
+     * Examples:
+     *     (a/b/c, a.b.c) gives true
+     *     (i/j/k, i.x.k) gives false
+     *
+     * Currently (x/a/b/c, a.b.c) also gives true. See JDK-8059598.
+     */
+    private boolean checkPathAndPackage(Path dir, JCTree pkgName) {
+        Iterator<String> pathIter = new ParentIterator(dir);
+        Iterator<String> pkgIter = new EnclosingPkgIterator(pkgName);
+        while (pathIter.hasNext() && pkgIter.hasNext()) {
+            if (!pathIter.next().equals(pkgIter.next()))
+                return false;
+        }
+        return !pkgIter.hasNext(); /*&& !pathIter.hasNext() See JDK-8059598 */
+    }
+
+    public Set<CompilationUnitTree> getMisplacedCompilationUnits() {
+        return misplacedCompilationUnits;
+    }
+
+    /* Iterates over the names of the parents of the given path:
+     * Example: dir1/dir2/dir3  results in  dir3 -> dir2 -> dir1
+     */
+    private static class ParentIterator implements Iterator<String> {
+        Path next;
+        ParentIterator(Path initial) {
+            next = initial;
+        }
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+        @Override
+        public String next() {
+            String tmp = next.getFileName().toString();
+            next = next.getParent();
+            return tmp;
+        }
+    }
+
+    /* Iterates over the names of the enclosing packages:
+     * Example: pkg1.pkg2.pkg3  results in  pkg3 -> pkg2 -> pkg1
+     */
+    private static class EnclosingPkgIterator implements Iterator<String> {
+        JCTree next;
+        EnclosingPkgIterator(JCTree initial) {
+            next = initial;
+        }
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+        @Override
+        public String next() {
+            Name name;
+            if (next instanceof JCIdent) {
+                name = ((JCIdent) next).name;
+                next = null;
+            } else {
+                JCFieldAccess fa = (JCFieldAccess) next;
+                name = fa.name;
+                next = fa.selected;
+            }
+            return name.toString();
+        }
+    }
+}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacErrorHandler.java	Tue Oct 07 21:15:10 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.tools.sjavac.comp;
-
-/**
- *  <p><b>This is NOT part of any supported API.
- *  If you write code that depends on this, you do so at your own risk.
- *  This code and its internal interfaces are subject to change or
- *  deletion without notice.</b>
- */
-public interface SjavacErrorHandler {
-    void logError(String msg);
-}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java	Tue Oct 07 21:15:10 2014 +0200
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/SjavacImpl.java	Tue Oct 07 21:21:42 2014 +0200
@@ -28,22 +28,25 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.URI;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.tools.JavaFileObject;
 import javax.tools.StandardJavaFileManager;
 
+import com.sun.source.tree.CompilationUnitTree;
 import com.sun.tools.javac.api.JavacTaskImpl;
 import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.PackageSymbol;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Options;
 import com.sun.tools.sjavac.Util;
 import com.sun.tools.sjavac.comp.dependencies.DependencyCollector;
+import com.sun.tools.sjavac.comp.dependencies.PublicApiCollector;
 import com.sun.tools.sjavac.server.CompilationResult;
 import com.sun.tools.sjavac.server.Sjavac;
 import com.sun.tools.sjavac.server.SysInfo;
@@ -72,18 +75,10 @@
                                      List<File> explicitSources,
                                      Set<URI> sourcesToCompile,
                                      Set<URI> visibleSources) {
-        final AtomicBoolean forcedExit = new AtomicBoolean();
-
         JavacTool compiler = JavacTool.create();
         StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
         SmartFileManager smartFileManager = new SmartFileManager(fileManager);
         Context context = new Context();
-        JavaCompilerWithDeps.preRegister(context, new SjavacErrorHandler() {
-            @Override
-            public void logError(String msg) {
-                forcedExit.set(true);
-            }
-        });
 
         // Now setup the actual compilation....
         CompilationResult compilationResult = new CompilationResult(0);
@@ -101,8 +96,6 @@
         for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) {
             compilationUnits.append(i);
         }
-        forcedExit.set(false);
-
 
         // Create a new logger.
         StringWriter stdoutLog = new StringWriter();
@@ -111,6 +104,8 @@
         PrintWriter stderr = new PrintWriter(stderrLog);
         com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK;
         DependencyCollector depsCollector = new DependencyCollector();
+        PublicApiCollector pubApiCollector = new PublicApiCollector();
+        PathAndPackageVerifier papVerifier = new PathAndPackageVerifier();
         try {
             if (compilationUnits.size() > 0) {
                 smartFileManager.setVisibleSources(visibleSources);
@@ -128,12 +123,14 @@
                                                          context);
                 smartFileManager.setSymbolFileEnabled(!Options.instance(context).isSet("ignore.symbol.file"));
                 task.addTaskListener(depsCollector);
+                task.addTaskListener(pubApiCollector);
+                task.addTaskListener(papVerifier);
                 rc = task.doCall();
                 smartFileManager.flush();
             }
         } catch (Exception e) {
             stderrLog.append(Util.getStackTrace(e));
-            forcedExit.set(true);
+            rc = com.sun.tools.javac.main.Main.Result.ERROR;
         }
 
         compilationResult.packageArtifacts = smartFileManager.getPackageArtifacts();
@@ -144,13 +141,23 @@
                 deps.collect(from.fullname, to.fullname);
         }
 
+        for (ClassSymbol cs : pubApiCollector.getClassSymbols())
+            deps.visitPubapi(cs);
+
+        if (papVerifier.getMisplacedCompilationUnits().size() > 0) {
+            for (CompilationUnitTree cu : papVerifier.getMisplacedCompilationUnits()) {
+                System.err.println("Misplaced compilation unit.");
+                System.err.println("    Directory: " + Paths.get(cu.getSourceFile().toUri()).getParent());
+                System.err.println("    Package:   " + cu.getPackageName());
+            }
+            rc = com.sun.tools.javac.main.Main.Result.ERROR;
+        }
+
         compilationResult.packageDependencies = deps.getDependencies();
         compilationResult.packagePubapis = deps.getPubapis();
-
         compilationResult.stdout = stdoutLog.toString();
         compilationResult.stderr = stderrLog.toString();
-
-        compilationResult.returnCode = rc.exitCode == 0 && forcedExit.get() ? -1 : rc.exitCode;
+        compilationResult.returnCode = rc.exitCode;
 
         return compilationResult;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/PublicApiCollector.java	Tue Oct 07 21:21:42 2014 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.tools.sjavac.comp.dependencies;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+
+public class PublicApiCollector implements TaskListener {
+
+    final Set<ClassSymbol> classSymbols = new HashSet<>();
+
+    @Override
+    @DefinedBy(Api.COMPILER_TREE)
+    public void started(TaskEvent e) {
+    }
+
+    @Override
+    @DefinedBy(Api.COMPILER_TREE)
+    public void finished(TaskEvent e) {
+        if (e.getKind() == TaskEvent.Kind.ANALYZE) {
+            for (Tree t : e.getCompilationUnit().getTypeDecls()) {
+                if (t instanceof JCClassDecl)  // Can also be a JCSkip
+                    classSymbols.add(((JCClassDecl) t).sym);
+            }
+        }
+    }
+
+    public Set<ClassSymbol> getClassSymbols() {
+        return classSymbols;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/sjavac/PackagePathMismatch.java	Tue Oct 07 21:21:42 2014 +0200
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8059349
+ * @summary This test makes sure file paths matches package declarations
+ * @library /tools/lib
+ * @build Wrapper ToolBox
+ * @run main Wrapper PackagePathMismatch
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class PackagePathMismatch extends SjavacBase {
+    public static void main(String... args) throws Exception {
+
+        Path root = Paths.get(PackagePathMismatch.class.getSimpleName() + "Test");
+        Path src = root.resolve("src");
+        Path classes = root.resolve("classes");
+
+        toolbox.writeFile(src.resolve("a/x/c/Test.java"),
+                          "package a.b.c; class Test { }");
+
+        // Compile should fail since package a.b.c does not match path a/x/c.
+        String server = "--server:portfile=testserver,background=false";
+        int rc1 = compile(server, "-d", classes, src);
+        if (rc1 == 0)
+            throw new AssertionError("Compilation succeeded unexpectedly");
+    }
+}