Add a knob to control warnings about unkeyed lists
[yangtools.git] / tools / yang-model-validator / src / main / java / org / opendaylight / yangtools / yang / validator / Main.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.validator;
9
10 import ch.qos.logback.classic.Level;
11 import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
12 import ch.qos.logback.classic.spi.ILoggingEvent;
13 import ch.qos.logback.core.FileAppender;
14 import com.google.common.base.Stopwatch;
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 import org.apache.commons.cli.CommandLine;
23 import org.apache.commons.cli.CommandLineParser;
24 import org.apache.commons.cli.DefaultParser;
25 import org.apache.commons.cli.HelpFormatter;
26 import org.apache.commons.cli.Option;
27 import org.apache.commons.cli.OptionGroup;
28 import org.apache.commons.cli.Options;
29 import org.apache.commons.cli.ParseException;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Main class of Yang parser system test.
37  *
38  * <p>
39  * yang-system-test [-f features] [-h help] [-p path] [-v verbose] yangFiles...
40  *  -f,--features &lt;arg&gt;   features is a string in the form
41  *                        [feature(,feature)*] and feature is a string in the form
42  *                        [($namespace?revision=$revision)$local_name].
43  *                        This option is used to prune the data model by removing
44  *                        all nodes that are defined with a "if-feature".
45  *  -h,--help             print help message and exit.
46  *  -p,--path &lt;arg&gt;       path is a colon (:) separated list of directories
47  *                        to search for yang modules.
48  *  -r, --recursive       recursive search of directories specified by -p option
49  *  -v, --verbose         shows details about the results of test running.
50  *  -o, --output          path to output file for logs. Output file will be overwritten.
51  *  -m, --module-name     validate yang by module name.
52  *  -wul, --warning-for-unkeyed-lists
53  *                        add warnings about unkeyed lists with config true.
54  */
55 @SuppressWarnings({"checkstyle:LoggerMustBeSlf4j", "checkstyle:LoggerFactoryClassParameter"})
56 public final class Main {
57     private static final Logger LOG = LoggerFactory.getLogger(Main.class);
58     private static final ch.qos.logback.classic.Logger LOG_ROOT =
59             (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
60     private static final int MB = 1024 * 1024;
61
62     private static final Option FEATURE = new Option("f", "features", true,
63         "features is a string in the form [feature(,feature)*] and feature is a string in the form "
64                 + "[($namespace?revision=$revision)$local_name]. This option is used to prune the data model "
65                 + "by removing all nodes that are defined with a \"if-feature\".");
66
67     private static final Option HELP = new Option("h", "help", false, "print help message and exit.");
68     private static final Option MODULE_NAME = new Option("m", "module-name", true,
69             "validate yang model by module name.");
70     private static final Option OUTPUT = new Option("o", "output", true,
71             "path to output file for logs. Output file will be overwritten");
72     private static final Option PATH = new Option("p", "path", true,
73             "path is a colon (:) separated list of directories to search for yang modules.");
74     private static final Option RECURSIVE = new Option("r", "recursive", false,
75             "recursive search of directories specified by -p option.");
76
77     private static final Option DEBUG = new Option("d", "debug", false, "add debug output");
78     private static final Option QUIET = new Option("q", "quiet", false, "completely suppress output.");
79     private static final Option VERBOSE = new Option("v", "verbose", false,
80         "shows details about the results of test running.");
81     private static final Option LIST_WARNING_ON = new Option("wul", "warning-for-unkeyed-lists", false,
82         "add warnings about unkeyed lists with config true");
83     private static final Option LIST_WARNING_OFF = new Option("no-wul", "no-warning-for-unkeyed-lists", false,
84         "do not add warnings about unkeyed lists with config true");
85
86     private Main() {
87         // Hidden on purpose
88     }
89
90     private static Options createOptions() {
91         return new Options()
92             .addOption(HELP)
93             .addOption(PATH)
94             .addOption(RECURSIVE)
95             .addOptionGroup(new OptionGroup().addOption(DEBUG).addOption(QUIET).addOption(VERBOSE))
96             .addOptionGroup(new OptionGroup().addOption(LIST_WARNING_ON).addOption(LIST_WARNING_OFF))
97             .addOption(OUTPUT)
98             .addOption(MODULE_NAME)
99             .addOption(FEATURE);
100     }
101
102     public static void main(final String[] args) {
103         final HelpFormatter formatter = new HelpFormatter();
104         final Options options = createOptions();
105         final CommandLine arguments = parseArguments(args, options, formatter);
106
107         if (arguments.hasOption(HELP.getLongOpt())) {
108             printHelp(options, formatter);
109             return;
110         }
111
112         final String[] outputValues = arguments.getOptionValues(OUTPUT.getLongOpt());
113         if (outputValues != null) {
114             setOutput(outputValues);
115         }
116
117         LOG_ROOT.setLevel(Level.WARN);
118         if (arguments.hasOption(DEBUG.getLongOpt())) {
119             LOG_ROOT.setLevel(Level.DEBUG);
120         } else if (arguments.hasOption(VERBOSE.getLongOpt())) {
121             LOG_ROOT.setLevel(Level.INFO);
122         } else if (arguments.hasOption(QUIET.getLongOpt())) {
123             LOG_ROOT.detachAndStopAllAppenders();
124         }
125
126         final boolean warnForUnkeyedLists;
127         if (arguments.hasOption(LIST_WARNING_ON.getLongOpt()) || !arguments.hasOption(LIST_WARNING_OFF.getLongOpt())) {
128             warnForUnkeyedLists = true;
129         } else {
130             warnForUnkeyedLists = false;
131         }
132
133         final List<String> yangLibDirs = initYangDirsPath(arguments);
134         final List<String> yangFiles = new ArrayList<>();
135         final String[] moduleNameValues = arguments.getOptionValues(MODULE_NAME.getLongOpt());
136         if (moduleNameValues != null) {
137             yangFiles.addAll(Arrays.asList(moduleNameValues));
138         }
139         yangFiles.addAll(Arrays.asList(arguments.getArgs()));
140
141         final Set<QName> supportedFeatures = initSupportedFeatures(arguments);
142
143         runSystemTest(yangLibDirs, yangFiles, supportedFeatures, arguments.hasOption(RECURSIVE.getLongOpt()),
144             warnForUnkeyedLists);
145
146         LOG_ROOT.getLoggerContext().reset();
147     }
148
149     private static void setOutput(final String... paths) {
150         LOG_ROOT.getLoggerContext().reset();
151
152         final PatternLayoutEncoder encoder = new PatternLayoutEncoder();
153         encoder.setPattern("%date %level [%thread] [%file:%line] %msg%n");
154         encoder.setContext(LOG_ROOT.getLoggerContext());
155         encoder.start();
156
157         for (final String path : paths) {
158             // create FileAppender
159             final FileAppender<ILoggingEvent> logfileOut = new FileAppender<>();
160             logfileOut.setAppend(false);
161             logfileOut.setFile(path);
162             logfileOut.setContext(LOG_ROOT.getLoggerContext());
163             logfileOut.setEncoder(encoder);
164             logfileOut.start();
165
166             // attach the rolling file appender to the root logger
167             LOG_ROOT.addAppender(logfileOut);
168         }
169     }
170
171     @SuppressFBWarnings({ "DM_EXIT", "DM_GC" })
172     @SuppressWarnings("checkstyle:illegalCatch")
173     private static void runSystemTest(final List<String> yangLibDirs, final List<String> yangFiles,
174             final Set<QName> supportedFeatures, final boolean recursiveSearch, final boolean warnForUnkeyedLists) {
175         LOG.info("Yang model dirs: {} ", yangLibDirs);
176         LOG.info("Yang model files: {} ", yangFiles);
177         LOG.info("Supported features: {} ", supportedFeatures);
178
179         EffectiveModelContext context = null;
180
181         printMemoryInfo("start");
182         final Stopwatch stopWatch = Stopwatch.createStarted();
183
184         try {
185             context = SystemTestUtils.parseYangSources(yangLibDirs, yangFiles, supportedFeatures,
186                     recursiveSearch, warnForUnkeyedLists);
187         } catch (final Exception e) {
188             LOG.error("Failed to create SchemaContext.", e);
189             System.exit(1);
190         }
191
192         stopWatch.stop();
193         LOG.info("Elapsed time: {}", stopWatch);
194         printMemoryInfo("end");
195         LOG.info("SchemaContext resolved Successfully. {}", context);
196         Runtime.getRuntime().gc();
197         printMemoryInfo("after gc");
198     }
199
200     private static List<String> initYangDirsPath(final CommandLine arguments) {
201         final List<String> yangDirs = new ArrayList<>();
202         if (arguments.hasOption("path")) {
203             for (final String pathArg : arguments.getOptionValues("path")) {
204                 yangDirs.addAll(Arrays.asList(pathArg.split(":")));
205             }
206         }
207         return yangDirs;
208     }
209
210     private static Set<QName> initSupportedFeatures(final CommandLine arguments) {
211         Set<QName> supportedFeatures = null;
212         if (arguments.hasOption("features")) {
213             supportedFeatures = new HashSet<>();
214             for (final String pathArg : arguments.getOptionValues("features")) {
215                 supportedFeatures.addAll(createQNames(pathArg.split(",")));
216             }
217         }
218         return supportedFeatures;
219     }
220
221     private static Collection<? extends QName> createQNames(final String[] featuresArg) {
222         final Set<QName> qnames = new HashSet<>();
223         for (final String featureStr : featuresArg) {
224             qnames.add(QName.create(featureStr));
225         }
226
227         return qnames;
228     }
229
230     @SuppressFBWarnings("DM_EXIT")
231     private static CommandLine parseArguments(final String[] args, final Options options,
232             final HelpFormatter formatter) {
233         final CommandLineParser parser = new DefaultParser();
234
235         CommandLine cmd = null;
236         try {
237             cmd = parser.parse(options, args);
238         } catch (final ParseException e) {
239             LOG.error("Failed to parse command line options.", e);
240             printHelp(options, formatter);
241             System.exit(1);
242         }
243
244         return cmd;
245     }
246
247     private static void printHelp(final Options options, final HelpFormatter formatter) {
248         formatter.printHelp("yang-system-test [OPTION...] YANG-FILE...", options);
249     }
250
251     private static void printMemoryInfo(final String info) {
252         LOG.info("Memory INFO [{}]: free {}MB, used {}MB, total {}MB, max {}MB", info,
253             Runtime.getRuntime().freeMemory() / MB,
254             (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / MB,
255             Runtime.getRuntime().totalMemory() / MB, Runtime.getRuntime().maxMemory() / MB);
256     }
257 }