Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ private void initCommands(List<String> disabledCommands) {
commandClassList.add(PwdCommand.class);
commandClassList.add(MBeanCommand.class);
commandClassList.add(GrepCommand.class);
commandClassList.add(WordCountCommand.class);
commandClassList.add(TeeCommand.class);
commandClassList.add(ProfilerCommand.class);
commandClassList.add(VmToolCommand.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.taobao.arthas.core.command.basic1000;

import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;

/**
* @see com.taobao.arthas.core.shell.command.internal.WordCountHandler
*/
@Name("wc")
@Summary("Count lines in command output.")
@Description(Constants.EXAMPLE +
" thread | wc -l\n" +
" sysprop | wc --lines\n"
+ Constants.WIKI + Constants.WIKI_HOME + "wc")
public class WordCountCommand extends AnnotatedCommand {

private boolean lineMode;

@Option(shortName = "l", longName = "lines", flag = true)
@Description("Count newline characters")
public void setLineMode(boolean lineMode) {
this.lineMode = lineMode;
}

public boolean isLineMode() {
return lineMode;
}

@Override
public void process(CommandProcess process) {
process.end(-1, "The wc command is only for pipes. See 'wc --help'\n");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.taobao.arthas.core.shell.command.internal;

import com.taobao.arthas.core.command.basic1000.WordCountCommand;
import com.taobao.arthas.core.shell.cli.CliToken;
import com.taobao.middleware.cli.CLIs;
import com.taobao.middleware.cli.CLI;
import com.taobao.middleware.cli.CommandLine;
import com.taobao.middleware.cli.Option;
import com.taobao.middleware.cli.annotations.CLIConfigurator;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
* @author ralf0131 2017-02-23 23:28.
Expand All @@ -15,16 +16,16 @@ public class WordCountHandler extends StdoutHandler implements StatisticsFunctio

public static final String NAME = "wc";

private static final CLI cli = CLIConfigurator.define(WordCountCommand.class, true);

private boolean lineMode;

private String result = null;
private final AtomicInteger total = new AtomicInteger(0);
private final AtomicLong total = new AtomicLong(0);

public static StdoutHandler inject(List<CliToken> tokens) {
List<String> args = StdoutHandler.parseArgs(tokens, NAME);
CommandLine commandLine = CLIs.create(NAME)
.addOption(new Option().setShortName("l").setFlag(true))
.parse(args);
CommandLine commandLine = cli.parse(args);
boolean lineMode = commandLine.isFlagEnabled("l");
return new WordCountHandler(lineMode);
}
Expand All @@ -39,8 +40,14 @@ public String apply(String input) {
// TODO the default behavior should be equivalent to `wc -l -w -c`
result = "wc currently only support wc -l!\n";
} else {
if (input != null && !"".equals(input.trim())) {
total.getAndAdd(input.split("\n").length);
if (input != null) {
int lines = 0;
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) == '\n') {
lines++;
}
}
total.addAndGet(lines);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import com.taobao.arthas.core.shell.command.ShellInternalCommandResolver;
import com.taobao.arthas.core.shell.command.internal.GrepHandler;
import com.taobao.arthas.core.shell.command.internal.PlainTextHandler;
import com.taobao.arthas.core.shell.command.internal.WordCountHandler;
import com.taobao.arthas.core.shell.handlers.Handler;
import com.taobao.arthas.core.shell.handlers.NoOpHandler;

Expand All @@ -34,7 +33,6 @@ public List<Command> commands() {
CommandBuilder.command("bg").processHandler(handler).build(),
CommandBuilder.command("kill").processHandler(handler).build(),
CommandBuilder.command(PlainTextHandler.NAME).processHandler(handler).build(),
CommandBuilder.command(GrepHandler.NAME).processHandler(handler).build(),
CommandBuilder.command(WordCountHandler.NAME).processHandler(handler).build());
CommandBuilder.command(GrepHandler.NAME).processHandler(handler).build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.taobao.arthas.core.shell.command.internal;

import com.taobao.arthas.core.shell.cli.CliTokens;
import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class WordCountHandlerTest {

@Test
public void testShortAndLongOptionsCountLines() {
for (String option : new String[] { "-l", "--lines" }) {
StdoutHandler handler = StdoutHandler.inject(CliTokens.tokenize("wc " + option));

Assert.assertTrue(handler instanceof StatisticsFunction);
Assert.assertNull(handler.apply("first\nsecond\n"));
Assert.assertNull(handler.apply("third\n"));
Assert.assertEquals("3\n", ((StatisticsFunction) handler).result());
}
}

@Test
public void testEmptyInput() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));

Assert.assertNull(handler.apply(null));
Assert.assertNull(handler.apply(""));
Assert.assertEquals("0\n", ((StatisticsFunction) handler).result());
}

@Test
public void testCountsBlankAndWhitespaceOnlyLines() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));

handler.apply("\n");
handler.apply(" \n\t\n");

Assert.assertEquals("3\n", ((StatisticsFunction) handler).result());
}

@Test
public void testCountsTrailingBlankLines() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));

handler.apply("first\n\n\n");

Assert.assertEquals("3\n", ((StatisticsFunction) handler).result());
}

@Test
public void testDoesNotCountUnterminatedText() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));

handler.apply("first");
Assert.assertEquals("0\n", ((StatisticsFunction) handler).result());
handler.apply("\nsecond");
Assert.assertEquals("1\n", ((StatisticsFunction) handler).result());
}

@Test
public void testCountDoesNotDependOnWriteBoundaries() {
String output = "first\r\n\nsecond\nlast";
for (int split = 0; split <= output.length(); split++) {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc --lines"));
handler.apply(output.substring(0, split));
handler.apply(output.substring(split));

Assert.assertEquals("split at " + split, "3\n", ((StatisticsFunction) handler).result());
}

StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));
for (int i = 0; i < output.length(); i++) {
handler.apply(output.substring(i, i + 1));
}
Assert.assertEquals("3\n", ((StatisticsFunction) handler).result());
}

@Test
public void testCarriageReturnDoesNotAddALine() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));

handler.apply("first\rsecond\r");

Assert.assertEquals("0\n", ((StatisticsFunction) handler).result());
}

@Test
public void testConcurrentWritesPreserveTheTotal() throws Exception {
final StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l"));
ExecutorService executor = Executors.newFixedThreadPool(4);
try {
List<Future<Void>> writes = new ArrayList<Future<Void>>();
for (int thread = 0; thread < 4; thread++) {
writes.add(executor.submit(new Callable<Void>() {
@Override
public Void call() {
for (int i = 0; i < 1000; i++) {
handler.apply("first\n\n");
}
return null;
}
}));
}
for (Future<Void> write : writes) {
write.get(5, TimeUnit.SECONDS);
}
Assert.assertEquals("8000\n", ((StatisticsFunction) handler).result());
} finally {
executor.shutdownNow();
}
}

@Test
public void testLineModeMustBeRequested() {
StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc"));

Assert.assertNull(handler.apply("first\n"));
Assert.assertEquals("wc currently only support wc -l!\n", ((StatisticsFunction) handler).result());
}

@Test
public void testCountersAreIndependent() {
StdoutHandler first = WordCountHandler.inject(CliTokens.tokenize("wc -l"));
StdoutHandler second = WordCountHandler.inject(CliTokens.tokenize("wc --lines"));

first.apply("first\nsecond\n");
second.apply("third\n");

Assert.assertEquals("2\n", ((StatisticsFunction) first).result());
Assert.assertEquals("1\n", ((StatisticsFunction) second).result());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.taobao.arthas.core.shell.impl;

import com.taobao.arthas.core.command.BuiltinCommandPack;
import com.taobao.arthas.core.command.basic1000.HelpCommand;
import com.taobao.arthas.core.command.basic1000.WordCountCommand;
import com.taobao.arthas.core.command.model.CommandOptionVO;
import com.taobao.arthas.core.command.model.CommandVO;
import com.taobao.arthas.core.command.model.HelpModel;
import com.taobao.arthas.core.shell.command.Command;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.command.CommandResolver;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.shell.system.impl.InternalCommandManager;
import com.taobao.middleware.cli.CommandLine;
import com.taobao.middleware.cli.annotations.CLIConfigurator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class WordCountCommandTest {

private List<CommandResolver> resolvers;
private Command command;

@Before
public void setUp() {
resolvers = Arrays.<CommandResolver>asList(new BuiltinCommandResolver(),
new BuiltinCommandPack(Collections.<String>emptyList()));
command = new InternalCommandManager(resolvers).getCommand("wc");
Assert.assertNotNull(command);
}

@Test
public void testHelpListsWcOnce() {
HelpModel help = help(null);
int count = 0;
for (CommandVO entry : help.getCommands()) {
if ("wc".equals(entry.getName())) {
Assert.assertNotNull(entry.getSummary());
count++;
}
}
Assert.assertEquals(1, count);
}

@Test
public void testHelpWcDescribesOptionsAndExamples() {
CommandVO detail = help("wc").getDetailCommand();

Assert.assertEquals("wc", detail.getName());
Assert.assertNotNull(detail.cli());
Assert.assertTrue(detail.getDescription().contains("thread | wc -l"));
Assert.assertTrue(detail.getDescription().contains("https://arthas.aliyun.com/doc/wc"));
Assert.assertTrue(detail.getUsage().contains("-l"));
Assert.assertTrue(detail.getUsage().contains("-h"));
boolean linesFound = false;
boolean helpFound = false;
for (CommandOptionVO option : detail.getOptions()) {
if ("l".equals(option.getShortName())) {
Assert.assertEquals("lines", option.getLongName());
linesFound = true;
}
if ("h".equals(option.getShortName())) {
Assert.assertEquals("help", option.getLongName());
helpFound = true;
}
}
Assert.assertTrue(linesFound);
Assert.assertTrue(helpFound);
}

@Test
public void testLineOptionsUseTheSameCommandDefinition() {
for (String option : new String[] { "-l", "--lines" }) {
WordCountCommand instance = new WordCountCommand();
CommandLine commandLine = command.cli().parse(Collections.singletonList(option));
CLIConfigurator.inject(commandLine, instance);
Assert.assertTrue(instance.isLineMode());
}
}

@Test
public void testStandaloneCommandExplainsPipeUsage() {
CommandProcess process = Mockito.mock(CommandProcess.class);

new WordCountCommand().process(process);

Mockito.verify(process).end(-1, "The wc command is only for pipes. See 'wc --help'\n");
}

private HelpModel help(String name) {
Session session = Mockito.mock(Session.class);
Mockito.when(session.getCommandResolvers()).thenReturn(resolvers);
CommandProcess process = Mockito.mock(CommandProcess.class);
Mockito.when(process.session()).thenReturn(session);
HelpCommand help = new HelpCommand();
help.setCmd(name);

help.process(process);

ArgumentCaptor<HelpModel> result = ArgumentCaptor.forClass(HelpModel.class);
Mockito.verify(process).appendResult(result.capture());
Mockito.verify(process).end();
return result.getValue();
}
}
Loading
Loading